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 +1 -1
- package/dist/Contract.js +5 -2
- package/dist/Errors.d.ts +11 -9
- package/dist/Errors.js +63 -57
- package/dist/Transaction.js +20 -24
- package/dist/TransactionBuilder.js +3 -2
- package/dist/debugging.js +62 -29
- package/dist/utils.d.ts +0 -2
- package/dist/utils.js +2 -28
- package/package.json +3 -3
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/
|
|
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
|
-
|
|
14
|
-
constructor(reason: string,
|
|
13
|
+
bitauthUri?: string | undefined;
|
|
14
|
+
constructor(reason: string, bitauthUri?: string | undefined);
|
|
15
15
|
}
|
|
16
|
-
export declare class FailedRequireError extends
|
|
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
|
|
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,
|
|
18
|
-
super(
|
|
17
|
+
constructor(reason, bitauthUri) {
|
|
18
|
+
super(`${reason}\n\nBitauth URI: ${bitauthUri}`);
|
|
19
19
|
this.reason = reason;
|
|
20
|
-
this.
|
|
20
|
+
this.bitauthUri = bitauthUri;
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
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
|
|
31
|
-
(function (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
})(
|
|
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
|
package/dist/Transaction.js
CHANGED
|
@@ -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,
|
|
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 (
|
|
117
|
-
|
|
118
|
-
|
|
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 {
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
35
|
-
const requireStatement = (artifact.debug?.
|
|
36
|
-
.find((
|
|
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
|
-
|
|
46
|
+
const { program: { inputIndex }, error } = lastExecutedDebugStep;
|
|
47
|
+
throw new FailedRequireError(artifact.contractName, requireStatement, inputIndex, error);
|
|
39
48
|
}
|
|
40
|
-
throw new
|
|
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
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
throw new
|
|
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
|
|
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
|
|
79
|
+
throw new FailedTransactionError(scenarioGeneration);
|
|
71
80
|
}
|
|
72
81
|
if (typeof scenarioGeneration.scenario === 'string') {
|
|
73
|
-
throw new
|
|
82
|
+
throw new FailedTransactionError(scenarioGeneration.scenario);
|
|
74
83
|
}
|
|
75
84
|
return { vm, program: scenarioGeneration.scenario.program };
|
|
76
85
|
};
|
|
77
|
-
const logConsoleLogStatement = (log,
|
|
86
|
+
const logConsoleLogStatement = (log, debugSteps, artifact) => {
|
|
78
87
|
let line = `${artifact.contractName}.cash:${log.line}`;
|
|
79
|
-
const decodedData = log.data.map((element) =>
|
|
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
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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.
|
|
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.
|
|
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": "
|
|
64
|
+
"gitHead": "b26f1f20b89df6d857a5860eebd3bcc7668faa4e"
|
|
65
65
|
}
|