cashscript 0.10.0-next.5 → 0.10.0-next.6

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
@@ -8,7 +8,7 @@
8
8
 
9
9
  CashScript is a high-level programming language for smart contracts on Bitcoin Cash. It offers a strong abstraction layer over Bitcoin Cash' native virtual machine, Bitcoin Script. Its syntax is based on Ethereum's smart contract language Solidity, but its functionality is very different since smart contracts on Bitcoin Cash differ greatly from smart contracts on Ethereum. For a detailed comparison of them, refer to the blog post [*Smart Contracts on Ethereum, Bitcoin and Bitcoin Cash*](https://kalis.me/smart-contracts-eth-btc-bch/).
10
10
 
11
- See the [GitHub repository](https://github.com/Bitcoin-com/cashscript) and the [CashScript website](https://cashscript.org) for full documentation and usage examples.
11
+ See the [GitHub repository](https://github.com/CashScript/cashscript) and the [CashScript website](https://cashscript.org) for full documentation and usage examples.
12
12
 
13
13
  ## The CashScript Language
14
14
  CashScript is a high-level language that allows you to write Bitcoin Cash smart contracts in a straightforward and familiar way. Its syntax is inspired by Ethereum's Solidity language, but its functionality is different since the underlying systems have very different fundamentals. See the [language documentation](https://cashscript.org/docs/language/) for a full reference of the language.
package/dist/Contract.js CHANGED
@@ -16,7 +16,7 @@ export class Contract {
16
16
  throw new Error('Invalid or incomplete artifact provided');
17
17
  }
18
18
  if (artifact.constructorInputs.length !== constructorArgs.length) {
19
- throw new Error(`Incorrect number of arguments passed to ${artifact.contractName} constructor`);
19
+ throw new Error(`Incorrect number of arguments passed to ${artifact.contractName} constructor. Expected ${artifact.constructorInputs.length} arguments (${artifact.constructorInputs.map(input => input.type)}) but got ${constructorArgs.length}`);
20
20
  }
21
21
  // Encode arguments (this also performs type checking)
22
22
  this.encodedConstructorArgs = encodeConstructorArguments(artifact, constructorArgs);
@@ -62,7 +62,7 @@ export class Contract {
62
62
  createFunction(abiFunction, selector) {
63
63
  return (...args) => {
64
64
  if (abiFunction.inputs.length !== args.length) {
65
- throw new Error(`Incorrect number of arguments passed to function ${abiFunction.name}`);
65
+ throw new Error(`Incorrect number of arguments passed to function ${abiFunction.name}. Expected ${abiFunction.inputs.length} arguments (${abiFunction.inputs.map(input => input.type)}) but got ${args.length}`);
66
66
  }
67
67
  // Encode passed args (this also performs type checking)
68
68
  const encodedArgs = encodeFunctionArguments(abiFunction, args);
@@ -72,6 +72,9 @@ export class Contract {
72
72
  }
73
73
  createUnlocker(abiFunction, selector) {
74
74
  return (...args) => {
75
+ if (abiFunction.inputs.length !== args.length) {
76
+ throw new Error(`Incorrect number of arguments passed to function ${abiFunction.name}. Expected ${abiFunction.inputs.length} arguments (${abiFunction.inputs.map(input => input.type)}) but got ${args.length}`);
77
+ }
75
78
  const bytecode = scriptToBytecode(this.redeemScript);
76
79
  const encodedArgs = args
77
80
  .map((arg, i) => encodeArgument(arg, abiFunction.inputs[i].type));
package/dist/Errors.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Type } from '@cashscript/utils';
1
+ import { RequireStatement, Type } from '@cashscript/utils';
2
2
  export declare class TypeError extends Error {
3
3
  constructor(actual: string, expected: Type);
4
4
  }
@@ -10,16 +10,18 @@ export declare class TokensToNonTokenAddressError extends Error {
10
10
  }
11
11
  export declare class FailedTransactionError extends Error {
12
12
  reason: string;
13
- debugStr?: string | undefined;
14
- constructor(reason: string, debugStr?: string | undefined);
13
+ bitauthUri?: string | undefined;
14
+ constructor(reason: string, bitauthUri?: string | undefined);
15
15
  }
16
- export declare class FailedRequireError extends FailedTransactionError {
16
+ export declare class FailedRequireError extends Error {
17
+ contractName: string;
18
+ requireStatement: RequireStatement;
19
+ inputIndex: number;
20
+ libauthErrorMessage?: string | undefined;
21
+ bitauthUri?: string;
22
+ constructor(contractName: string, requireStatement: RequireStatement, inputIndex: number, libauthErrorMessage?: string | undefined);
17
23
  }
18
- export declare class FailedTimeCheckError extends FailedTransactionError {
19
- }
20
- export declare class FailedSigCheckError extends FailedTransactionError {
21
- }
22
- export declare enum Reason {
24
+ export declare enum NodeErrorReason {
23
25
  EVAL_FALSE = "Script evaluated without error but finished with a false/empty top stack element",
24
26
  VERIFY = "Script failed an OP_VERIFY operation",
25
27
  EQUALVERIFY = "Script failed an OP_EQUALVERIFY operation",
package/dist/Errors.js CHANGED
@@ -14,66 +14,72 @@ export class TokensToNonTokenAddressError extends Error {
14
14
  }
15
15
  }
16
16
  export class FailedTransactionError extends Error {
17
- constructor(reason, debugStr) {
18
- super(`Transaction failed with reason: ${reason}${debugStr ? `\n\n${debugStr}` : ''}`);
17
+ constructor(reason, bitauthUri) {
18
+ super(`${reason}\n\nBitauth URI: ${bitauthUri}`);
19
19
  this.reason = reason;
20
- this.debugStr = debugStr;
20
+ this.bitauthUri = bitauthUri;
21
21
  }
22
22
  }
23
- export class FailedRequireError extends FailedTransactionError {
24
- }
25
- export class FailedTimeCheckError extends FailedTransactionError {
26
- }
27
- export class FailedSigCheckError extends FailedTransactionError {
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;
30
+ this.requireStatement = requireStatement;
31
+ this.inputIndex = inputIndex;
32
+ this.libauthErrorMessage = libauthErrorMessage;
33
+ }
28
34
  }
29
35
  // TODO: Expand these reasons with non-script failures (like tx-mempool-conflict)
30
- export var Reason;
31
- (function (Reason) {
32
- Reason["EVAL_FALSE"] = "Script evaluated without error but finished with a false/empty top stack element";
33
- Reason["VERIFY"] = "Script failed an OP_VERIFY operation";
34
- Reason["EQUALVERIFY"] = "Script failed an OP_EQUALVERIFY operation";
35
- Reason["CHECKMULTISIGVERIFY"] = "Script failed an OP_CHECKMULTISIGVERIFY operation";
36
- Reason["CHECKSIGVERIFY"] = "Script failed an OP_CHECKSIGVERIFY operation";
37
- Reason["CHECKDATASIGVERIFY"] = "Script failed an OP_CHECKDATASIGVERIFY operation";
38
- Reason["NUMEQUALVERIFY"] = "Script failed an OP_NUMEQUALVERIFY operation";
39
- Reason["SCRIPT_SIZE"] = "Script is too big";
40
- Reason["PUSH_SIZE"] = "Push value size limit exceeded";
41
- Reason["OP_COUNT"] = "Operation limit exceeded";
42
- Reason["STACK_SIZE"] = "Stack size limit exceeded";
43
- Reason["SIG_COUNT"] = "Signature count negative or greater than pubkey count";
44
- Reason["PUBKEY_COUNT"] = "Pubkey count negative or limit exceeded";
45
- Reason["INVALID_OPERAND_SIZE"] = "Invalid operand size";
46
- Reason["INVALID_NUMBER_RANGE"] = "Given operand is not a number within the valid range";
47
- Reason["IMPOSSIBLE_ENCODING"] = "The requested encoding is impossible to satisfy";
48
- Reason["INVALID_SPLIT_RANGE"] = "Invalid OP_SPLIT range";
49
- Reason["INVALID_BIT_COUNT"] = "Invalid number of bit set in OP_CHECKMULTISIG";
50
- Reason["BAD_OPCODE"] = "Opcode missing or not understood";
51
- Reason["DISABLED_OPCODE"] = "Attempted to use a disabled opcode";
52
- Reason["INVALID_STACK_OPERATION"] = "Operation not valid with the current stack size";
53
- Reason["INVALID_ALTSTACK_OPERATION"] = "Operation not valid with the current altstack size";
54
- Reason["OP_RETURN"] = "OP_RETURN was encountered";
55
- Reason["UNBALANCED_CONDITIONAL"] = "Invalid OP_IF construction";
56
- Reason["DIV_BY_ZERO"] = "Division by zero error";
57
- Reason["MOD_BY_ZERO"] = "Modulo by zero error";
58
- Reason["INVALID_BITFIELD_SIZE"] = "Bitfield of unexpected size error";
59
- Reason["INVALID_BIT_RANGE"] = "Bitfield's bit out of the expected range";
60
- Reason["NEGATIVE_LOCKTIME"] = "Negative locktime";
61
- Reason["UNSATISFIED_LOCKTIME"] = "Locktime requirement not satisfied";
62
- Reason["SIG_HASHTYPE"] = "Signature hash type missing or not understood";
63
- Reason["SIG_DER"] = "Non-canonical DER signature";
64
- Reason["MINIMALDATA"] = "Data push larger than necessary";
65
- Reason["SIG_PUSHONLY"] = "Only push operators allowed in signature scripts";
66
- Reason["SIG_HIGH_S"] = "Non-canonical signature: S value is unnecessarily high";
67
- Reason["MINIMALIF"] = "OP_IF/NOTIF argument must be minimal";
68
- Reason["SIG_NULLFAIL"] = "Signature must be zero for failed CHECK(MULTI)SIG operation";
69
- Reason["SIG_BADLENGTH"] = "Signature cannot be 65 bytes in CHECKMULTISIG";
70
- Reason["SIG_NONSCHNORR"] = "Only Schnorr signatures allowed in this operation";
71
- Reason["DISCOURAGE_UPGRADABLE_NOPS"] = "NOPx reserved for soft-fork upgrades";
72
- Reason["PUBKEYTYPE"] = "Public key is neither compressed or uncompressed";
73
- Reason["CLEANSTACK"] = "Script did not clean its stack";
74
- Reason["NONCOMPRESSED_PUBKEY"] = "Using non-compressed public key";
75
- Reason["ILLEGAL_FORKID"] = "Illegal use of SIGHASH_FORKID";
76
- Reason["MUST_USE_FORKID"] = "Signature must use SIGHASH_FORKID";
77
- Reason["UNKNOWN"] = "unknown error";
78
- })(Reason || (Reason = {}));
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 = {}));
79
85
  //# sourceMappingURL=Errors.js.map
@@ -4,13 +4,14 @@ import delay from 'delay';
4
4
  import { placeholder, scriptToBytecode, } from '@cashscript/utils';
5
5
  import deepEqual from 'fast-deep-equal';
6
6
  import { isUtxoP2PKH, } from './interfaces.js';
7
- import { createInputScript, getInputSize, createOpReturnOutput, getTxSizeWithoutInputs, getPreimageSize, buildError, validateOutput, utxoComparator, calculateDust, getOutputSize, utxoTokenComparator, } from './utils.js';
7
+ import { createInputScript, getInputSize, createOpReturnOutput, getTxSizeWithoutInputs, getPreimageSize, validateOutput, utxoComparator, calculateDust, getOutputSize, utxoTokenComparator, } from './utils.js';
8
8
  import SignatureTemplate from './SignatureTemplate.js';
9
9
  import { P2PKH_INPUT_SIZE } from './constants.js';
10
10
  import { TransactionBuilder } from './TransactionBuilder.js';
11
11
  import MockNetworkProvider from './network/MockNetworkProvider.js';
12
12
  import { buildTemplate, getBitauthUri } from './LibauthTemplate.js';
13
13
  import { debugTemplate, evaluateTemplate } from './debugging.js';
14
+ import { FailedRequireError, FailedTransactionError } from './Errors.js';
14
15
  export class Transaction {
15
16
  constructor(contract, unlocker, abiFunction, encodedFunctionArgs, selector) {
16
17
  this.contract = contract;
@@ -102,7 +103,22 @@ export class Transaction {
102
103
  async send(raw) {
103
104
  const tx = await this.build();
104
105
  let template;
106
+ // Debug the transaction locally before sending so any errors are caught early
105
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;
119
+ }
120
+ try {
121
+ // TODO: Can we move this to MockNetworkProvider?
106
122
  if (this.contract.provider instanceof MockNetworkProvider) {
107
123
  template = await buildTemplate({
108
124
  transaction: this,
@@ -113,29 +129,9 @@ export class Transaction {
113
129
  const txid = await this.contract.provider.sendRawTransaction(tx);
114
130
  return raw ? await this.getTxDetails(txid, raw) : await this.getTxDetails(txid);
115
131
  }
116
- catch (maybeNodeError) {
117
- if (!template) {
118
- template = await buildTemplate({
119
- transaction: this,
120
- transactionHex: tx,
121
- });
122
- }
123
- const reason = maybeNodeError.error ?? maybeNodeError.message ?? maybeNodeError;
124
- const bitauthUri = getBitauthUri(template);
125
- try {
126
- debugTemplate(template, this.contract.artifact);
127
- }
128
- catch (libauthError) {
129
- if (this.contract.provider instanceof MockNetworkProvider) {
130
- throw buildError(libauthError, bitauthUri);
131
- }
132
- else {
133
- const message = `${libauthError}\n\nUnderlying node error: ${reason}`;
134
- throw buildError(message, bitauthUri);
135
- }
136
- }
137
- // this must be unreachable
138
- throw buildError(reason, bitauthUri);
132
+ catch (error) {
133
+ const reason = error.error ?? error.message ?? error;
134
+ throw new FailedTransactionError(reason, await this.bitauthUri());
139
135
  }
140
136
  }
141
137
  // method to debug the transaction with libauth VM, throws upon evaluation error
@@ -1,7 +1,8 @@
1
1
  import { binToHex, decodeTransaction, encodeTransaction, hexToBin, } from '@bitauth/libauth';
2
2
  import delay from 'delay';
3
3
  import { isUnlockableUtxo, } from './interfaces.js';
4
- import { buildError, cashScriptOutputToLibauthOutput, createOpReturnOutput, validateOutput, } from './utils.js';
4
+ import { cashScriptOutputToLibauthOutput, createOpReturnOutput, validateOutput, } from './utils.js';
5
+ import { FailedTransactionError } from './Errors.js';
5
6
  const DEFAULT_SEQUENCE = 0xfffffffe;
6
7
  export class TransactionBuilder {
7
8
  constructor(options) {
@@ -94,7 +95,7 @@ export class TransactionBuilder {
94
95
  }
95
96
  catch (e) {
96
97
  const reason = e.error ?? e.message;
97
- throw buildError(reason);
98
+ throw new FailedTransactionError(reason);
98
99
  }
99
100
  }
100
101
  async getTxDetails(txid, raw) {
package/dist/debugging.js CHANGED
@@ -1,12 +1,13 @@
1
- import { AuthenticationErrorCommon, binToHex, createCompiler, createVirtualMachineBCH2023, walletTemplateToCompilerConfiguration } from '@bitauth/libauth';
2
- import { Op, PrimitiveType, decodeBool, decodeInt, decodeString } from '@cashscript/utils';
1
+ import { AuthenticationErrorCommon, binToHex, createCompiler, createVirtualMachineBCH2023, encodeAuthenticationInstruction, walletTemplateToCompilerConfiguration } from '@bitauth/libauth';
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
5
  // evaluates the fully defined template, throws upon error
5
6
  export const evaluateTemplate = (template) => {
6
7
  const { vm, program } = createProgram(template);
7
8
  const verifyResult = vm.verify(program);
8
9
  if (typeof verifyResult === 'string') {
9
- throw new Error(verifyResult);
10
+ throw new FailedTransactionError(verifyResult);
10
11
  }
11
12
  return verifyResult;
12
13
  };
@@ -24,34 +25,42 @@ export const debugTemplate = (template, artifact) => {
24
25
  const executedLogs = (artifact.debug?.logs ?? [])
25
26
  .filter((debugStep) => executedDebugSteps.some((log) => log.ip === debugStep.ip));
26
27
  for (const log of executedLogs) {
27
- const correspondingDebugStep = executedDebugSteps.find((debugStep) => debugStep.ip === log.ip);
28
- logConsoleLogStatement(log, correspondingDebugStep, artifact);
28
+ logConsoleLogStatement(log, executedDebugSteps, artifact);
29
29
  }
30
30
  const lastExecutedDebugStep = executedDebugSteps[executedDebugSteps.length - 1];
31
31
  // If an error is present in the last step, that means a require statement in the middle of the function failed
32
32
  if (lastExecutedDebugStep.error) {
33
+ // In Libauth any thrown error gets registered in the instruction that happens right *after* the instruction that
34
+ // caused the error (in other words the OP_VERIFY). We need to decrement the instruction pointer to get the correct
35
+ // failing instruction.
36
+ const failingIp = lastExecutedDebugStep.ip - 1;
37
+ // Generally speaking, an error is thrown by the OP_VERIFY opcode, but for NULLFAIL, the error is thrown in the
38
+ // 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.
33
40
  const isNullFail = lastExecutedDebugStep.error === AuthenticationErrorCommon.nonNullSignatureFailure;
34
- const messageIp = lastExecutedDebugStep.ip + (isNullFail ? 1 : 0);
35
- const requireStatement = (artifact.debug?.requireMessages ?? [])
36
- .find((message) => message.ip === messageIp);
41
+ const requireStatementIp = failingIp + (isNullFail ? 1 : 0);
42
+ const requireStatement = (artifact.debug?.requires ?? [])
43
+ .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')")
37
45
  if (requireStatement) {
38
- throw new Error(`${artifact.contractName}.cash:${requireStatement.line} Error in evaluating input index ${lastExecutedDebugStep.program.inputIndex} with the following message: ${requireStatement.message}.\n${lastExecutedDebugStep.error}`);
46
+ const { program: { inputIndex }, error } = lastExecutedDebugStep;
47
+ throw new FailedRequireError(artifact.contractName, requireStatement, inputIndex, error);
39
48
  }
40
- throw new Error(`Error in evaluating input index ${lastExecutedDebugStep.program.inputIndex}.\n${lastExecutedDebugStep.error}`);
49
+ throw new FailedTransactionError(`Error in evaluating input index ${lastExecutedDebugStep.program.inputIndex}.\n${lastExecutedDebugStep.error}`);
41
50
  }
42
51
  const evaluationResult = vm.verify(program);
43
52
  if (failedFinalVerify(evaluationResult)) {
44
- const stackContents = lastExecutedDebugStep.stack.map(item => `0x${binToHex(item)}`).join(', ');
45
- const stackContentsMessage = `\nStack contents after evaluation: ${lastExecutedDebugStep.stack.length ? stackContents : 'empty'}`;
46
- const executedInstructions = lastExecutedDebugStep.instructions
47
- .filter((_, i) => executedDebugSteps.map((step) => step.ip).includes(i));
48
- const cleanupSize = calculateCleanupSize(executedInstructions);
49
- const finalExecutedVerify = executedDebugSteps[executedDebugSteps.length - cleanupSize - 1];
50
- const finalVerifyMessage = artifact.debug?.requireMessages.find((message) => message.ip === finalExecutedVerify.ip);
51
- if (finalVerifyMessage) {
52
- throw new Error(`${artifact.contractName}.cash:${finalVerifyMessage.line} Error in evaluating input index ${lastExecutedDebugStep.program.inputIndex} with the following message: ${finalVerifyMessage.message}.\n${evaluationResult.replace(/Error in evaluating input index \d: /, '')}` + stackContentsMessage);
53
+ const finalExecutedVerifyIp = getFinalExecutedVerifyIp(executedDebugSteps);
54
+ // logDebugSteps(executedDebugSteps, lastExecutedDebugStep.instructions);
55
+ // console.warn('message', finalExecutedVerifyIp);
56
+ // console.warn(artifact.debug?.requires);
57
+ const requireStatement = (artifact.debug?.requires ?? [])
58
+ .find((message) => message.ip === finalExecutedVerifyIp);
59
+ if (requireStatement) {
60
+ const { program: { inputIndex }, error } = lastExecutedDebugStep;
61
+ throw new FailedRequireError(artifact.contractName, requireStatement, inputIndex, error);
53
62
  }
54
- throw new Error(evaluationResult + stackContentsMessage);
63
+ throw new FailedTransactionError(`Error in evaluating input index ${lastExecutedDebugStep.program.inputIndex}.\n${evaluationResult}`);
55
64
  }
56
65
  return fullDebugSteps;
57
66
  };
@@ -67,25 +76,28 @@ const createProgram = (template) => {
67
76
  scenarioId: 'evaluate_function',
68
77
  });
69
78
  if (typeof scenarioGeneration === 'string') {
70
- throw new Error(scenarioGeneration);
79
+ throw new FailedTransactionError(scenarioGeneration);
71
80
  }
72
81
  if (typeof scenarioGeneration.scenario === 'string') {
73
- throw new Error(scenarioGeneration.scenario);
82
+ throw new FailedTransactionError(scenarioGeneration.scenario);
74
83
  }
75
84
  return { vm, program: scenarioGeneration.scenario.program };
76
85
  };
77
- const logConsoleLogStatement = (log, debugStep, artifact) => {
86
+ const logConsoleLogStatement = (log, debugSteps, artifact) => {
78
87
  let line = `${artifact.contractName}.cash:${log.line}`;
79
- const decodedData = log.data.map((element) => decodeLogData(element, debugStep.stack, log.ip));
88
+ const decodedData = log.data.map((element) => {
89
+ if (typeof element === 'string')
90
+ return element;
91
+ const debugStep = debugSteps.find((step) => step.ip === element.ip);
92
+ return decodeStackItem(element, debugStep.stack);
93
+ });
80
94
  console.log(`${line} ${decodedData.join(' ')}`);
81
95
  };
82
- const decodeLogData = (element, stack, ip) => {
83
- if (typeof element === 'string')
84
- return element;
96
+ const decodeStackItem = (element, stack) => {
85
97
  // Reversed since stack is in reverse order
86
98
  const stackItem = [...stack].reverse()[element.stackIndex];
87
99
  if (!stackItem) {
88
- throw Error(`Stack item at index ${element.stackIndex} not found at instruction pointer ${ip}`);
100
+ throw Error(`Stack item at index ${element.stackIndex} not found at instruction pointer ${element.ip}`);
89
101
  }
90
102
  if (element.type === PrimitiveType.BOOL)
91
103
  return decodeBool(stackItem);
@@ -113,7 +125,9 @@ const calculateCleanupSize = (instructions) => {
113
125
  const cleanupOpcodes = [Op.OP_ENDIF, Op.OP_NIP, Op.OP_ELSE];
114
126
  let cleanupSize = 0;
115
127
  for (const instruction of [...instructions].reverse()) {
116
- if (cleanupOpcodes.includes(instruction.opcode)) {
128
+ // The instructions array may contain undefined elements for the final executed debug steps that are not
129
+ // technically instructions (e.g. the final *implicit* verify). We still want to get rid of these in the cleanup
130
+ if (!instruction || cleanupOpcodes.includes(instruction.opcode)) {
117
131
  cleanupSize++;
118
132
  }
119
133
  else {
@@ -122,4 +136,23 @@ const calculateCleanupSize = (instructions) => {
122
136
  }
123
137
  return cleanupSize;
124
138
  };
139
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
140
+ const logDebugSteps = (debugSteps, instructions) => {
141
+ debugSteps.map((step) => {
142
+ const opcode = instructions[step.ip] ? bytecodeToAsm(encodeAuthenticationInstruction(instructions[step.ip])) : 'null';
143
+ console.warn(step.ip, opcode, step.stack, step.error);
144
+ });
145
+ };
146
+ const getFinalExecutedVerifyIp = (executedDebugSteps) => {
147
+ // Map every executed debug step to its corresponding instruction
148
+ // (note that the last executed step(s) may not have a corresponding instruction and will be `undefined`)
149
+ const executedInstructions = executedDebugSteps.map((step) => step.instructions[step.ip]);
150
+ const finalExecutedDebugStepIndex = executedDebugSteps.length - 1;
151
+ const cleanupSize = calculateCleanupSize(executedInstructions);
152
+ const finalExecutedNonCleanupStep = executedDebugSteps[finalExecutedDebugStepIndex - cleanupSize];
153
+ // The final verify in a function is dropped, so after cleanup, we get the final OP that was executed *before* the
154
+ // final verify, so we need to add one to get the actual final verify instruction
155
+ const finalExecutedVerifyIp = finalExecutedNonCleanupStep.ip + 1;
156
+ return finalExecutedVerifyIp;
157
+ };
125
158
  //# sourceMappingURL=debugging.js.map
package/dist/utils.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { Transaction } from '@bitauth/libauth';
2
2
  import { Script } from '@cashscript/utils';
3
3
  import { Utxo, Output, LibauthOutput, TokenDetails, AddressType } from './interfaces.js';
4
- import { FailedTransactionError } from './Errors.js';
5
4
  export declare function validateOutput(output: Output): void;
6
5
  export declare function calculateDust(output: Output): number;
7
6
  export declare function getOutputSize(output: Output): number;
@@ -14,7 +13,6 @@ export declare function getTxSizeWithoutInputs(outputs: Output[]): number;
14
13
  export declare function createInputScript(redeemScript: Script, encodedArgs: Uint8Array[], selector?: number, preimage?: Uint8Array): Uint8Array;
15
14
  export declare function createOpReturnOutput(opReturnData: string[]): Output;
16
15
  export declare function createSighashPreimage(transaction: Transaction, sourceOutputs: LibauthOutput[], inputIndex: number, coveredBytecode: Uint8Array, hashtype: number): Uint8Array;
17
- export declare function buildError(reason: string, debugStr?: string): FailedTransactionError;
18
16
  export declare function toRegExp(reasons: string[]): RegExp;
19
17
  export declare function scriptToAddress(script: Script, network: string, addressType: AddressType, tokenSupport: boolean): string;
20
18
  export declare function scriptToLockingBytecode(script: Script, addressType: AddressType): Uint8Array;
package/dist/utils.js CHANGED
@@ -1,8 +1,8 @@
1
- import { cashAddressToLockingBytecode, decodeCashAddress, addressContentsToLockingBytecode, lockingBytecodeToCashAddress, binToHex, generateSigningSerializationBCH, utf8ToBin, hexToBin, flattenBinArray, LockingBytecodeType, encodeTransactionOutput, isHex, bigIntToCompactUint, AuthenticationErrorCommon, NonFungibleTokenCapability, bigIntToVmNumber, } from '@bitauth/libauth';
1
+ import { cashAddressToLockingBytecode, decodeCashAddress, addressContentsToLockingBytecode, lockingBytecodeToCashAddress, binToHex, generateSigningSerializationBCH, utf8ToBin, hexToBin, flattenBinArray, LockingBytecodeType, encodeTransactionOutput, isHex, bigIntToCompactUint, NonFungibleTokenCapability, bigIntToVmNumber, } from '@bitauth/libauth';
2
2
  import { encodeInt, hash160, hash256, sha256, Op, scriptToBytecode, } from '@cashscript/utils';
3
3
  import { Network, } from './interfaces.js';
4
4
  import { VERSION_SIZE, LOCKTIME_SIZE } from './constants.js';
5
- import { OutputSatoshisTooSmallError, TokensToNonTokenAddressError, Reason, FailedTransactionError, FailedRequireError, FailedTimeCheckError, FailedSigCheckError, } from './Errors.js';
5
+ import { OutputSatoshisTooSmallError, TokensToNonTokenAddressError, } from './Errors.js';
6
6
  // ////////// PARAMETER VALIDATION ////////////////////////////////////////////
7
7
  export function validateOutput(output) {
8
8
  if (typeof output.to !== 'string')
@@ -135,32 +135,6 @@ export function createSighashPreimage(transaction, sourceOutputs, inputIndex, co
135
135
  const sighashPreimage = generateSigningSerializationBCH(context, { coveredBytecode, signingSerializationType });
136
136
  return sighashPreimage;
137
137
  }
138
- export function buildError(reason, debugStr) {
139
- const require = [
140
- Reason.EVAL_FALSE, Reason.VERIFY, Reason.EQUALVERIFY, Reason.CHECKMULTISIGVERIFY,
141
- Reason.CHECKSIGVERIFY, Reason.CHECKDATASIGVERIFY, Reason.NUMEQUALVERIFY,
142
- AuthenticationErrorCommon.failedVerify,
143
- ];
144
- const timeCheck = [
145
- Reason.NEGATIVE_LOCKTIME, Reason.UNSATISFIED_LOCKTIME,
146
- AuthenticationErrorCommon.negativeLocktime, AuthenticationErrorCommon.unsatisfiedLocktime,
147
- ];
148
- const sigCheck = [
149
- Reason.SIG_COUNT, Reason.PUBKEY_COUNT, Reason.SIG_HASHTYPE, Reason.SIG_DER,
150
- Reason.SIG_HIGH_S, Reason.SIG_NULLFAIL, Reason.SIG_BADLENGTH, Reason.SIG_NONSCHNORR,
151
- AuthenticationErrorCommon.nonNullSignatureFailure,
152
- ];
153
- if (toRegExp(require).test(reason)) {
154
- return new FailedRequireError(reason, debugStr);
155
- }
156
- if (toRegExp(timeCheck).test(reason)) {
157
- return new FailedTimeCheckError(reason, debugStr);
158
- }
159
- if (toRegExp(sigCheck).test(reason)) {
160
- return new FailedSigCheckError(reason, debugStr);
161
- }
162
- return new FailedTransactionError(reason, debugStr);
163
- }
164
138
  export function toRegExp(reasons) {
165
139
  return new RegExp(reasons.join('|').replace(/\(/g, '\\(').replace(/\)/g, '\\)'));
166
140
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cashscript",
3
- "version": "0.10.0-next.5",
3
+ "version": "0.10.0-next.6",
4
4
  "description": "Easily write and interact with Bitcoin Cash contracts",
5
5
  "keywords": [
6
6
  "bitcoin cash",
@@ -44,7 +44,7 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "@bitauth/libauth": "^2.0.0",
47
- "@cashscript/utils": "^0.10.0-next.5",
47
+ "@cashscript/utils": "^0.10.0-next.6",
48
48
  "bip68": "^1.0.4",
49
49
  "bitcoin-rpc-promise-retry": "^1.3.0",
50
50
  "delay": "^5.0.0",
@@ -61,5 +61,5 @@
61
61
  "jest": "^29.4.1",
62
62
  "typescript": "^4.1.5"
63
63
  },
64
- "gitHead": "3d6611410e8c7ed9d760071a8c639e91c2cbb477"
64
+ "gitHead": "b26f1f20b89df6d857a5860eebd3bcc7668faa4e"
65
65
  }