From e7e7f5da9b591cd0f667df99675efb2a263df546 Mon Sep 17 00:00:00 2001 From: Rosco Kalis Date: Tue, 18 Aug 2026 10:50:37 +0200 Subject: [PATCH 1/3] Remove old bytecode optimisation --- .cspell.json | 1 - AGENTS.md | 2 +- DEVELOPMENT.md | 18 --- packages/cashc/src/compiler.ts | 9 -- .../cashc/test/cashproof/0.1.2=0.2.0.equiv | 111 -------------- .../cashc/test/cashproof/0.3.3=0.4.0.equiv | 32 ---- packages/cashc/test/cashproof/slice.equiv | 14 -- packages/utils/src/cashproof-optimisations.ts | 139 ------------------ packages/utils/src/optimisations.ts | 20 +-- packages/utils/src/script.ts | 52 ------- 10 files changed, 7 insertions(+), 391 deletions(-) delete mode 100644 packages/cashc/test/cashproof/0.1.2=0.2.0.equiv delete mode 100644 packages/cashc/test/cashproof/0.3.3=0.4.0.equiv delete mode 100644 packages/cashc/test/cashproof/slice.equiv delete mode 100644 packages/utils/src/cashproof-optimisations.ts diff --git a/.cspell.json b/.cspell.json index 4764b7ab..3231913c 100644 --- a/.cspell.json +++ b/.cspell.json @@ -36,7 +36,6 @@ "cashaddress", "cashc", "cashlibs", - "cashproof", "cashscript", "cashtokens", "castable", diff --git a/AGENTS.md b/AGENTS.md index 61233412..3d84b4ba 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -84,7 +84,7 @@ Shared between compiler and SDK: - `source-map.ts` — source map encoding/decoding (format: `sl:sc:el:ec:h` per opcode, `;`-separated, with field inheritance compression) - `bitauth-script.ts` — formats bytecode as human-readable BitAuth script (used in debugging) - `types.ts` — shared types including `SourceTagKind`, `SourceTagEntry` -- `optimisations.ts` / `cashproof-optimisations.ts` — peephole optimization rules +- `optimisations.ts` — peephole optimization rules ## Code Conventions diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 95b52c5a..a202d27e 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -38,24 +38,6 @@ When updating the grammar file in `src/grammar/CashScript.g4`, we also need to m yarn antlr ``` -### Running `cashproof` - -Most of the bytecode optimisations that the `cashc` compiler uses can be verified for correctness using the [`cashproof` tool](https://github.com/EyeOfPython/cashproof). This tool needs to be installed separately by installing its dependencies using `pip` and cloning its repository from GitHub. - -From there, you can run `python [filenames]` to verify that the optimisations contained in these files are provably correct. - -Example: -```bash -python packages/cashc/test/cashproof/0.1.2=0.2.0.equiv -``` - -Note that if you want to run `cashproof` on the "main" CashScript optimisations file, you need to first extract the optimisations from the `cashc` compiler and save them in a separate file. This can be done using the following commands: - -```bash -cp packages/utils/src/cashproof-optimisations.ts opt.equiv && sed -i '' '/`/d' opt.equiv -python opt.equiv -``` - ## cashscript ### Running tests diff --git a/packages/cashc/src/compiler.ts b/packages/cashc/src/compiler.ts index 168c5bb9..6724d8c1 100644 --- a/packages/cashc/src/compiler.ts +++ b/packages/cashc/src/compiler.ts @@ -7,8 +7,6 @@ import { generateSourceTags, generateInlineRanges, optimiseBytecode, - optimiseBytecodeOld, - scriptToAsm, scriptToBytecode, sourceMapToLocationData, } from '@cashscript/utils'; @@ -139,7 +137,6 @@ function compileCode( ast = ast.accept(traversal) as Ast; // Bytecode optimisation - const optimisedBytecodeOld = optimiseBytecodeOld(traversal.output); const optimisationResult = optimiseBytecode( traversal.output, sourceMapToLocationData(traversal.sourceMap), @@ -150,12 +147,6 @@ function compileCode( constructorParamLength, ); - if (scriptToAsm(optimisedBytecodeOld) !== scriptToAsm(optimisationResult.script)) { - console.error(scriptToAsm(optimisedBytecodeOld)); - console.error(scriptToAsm(optimisationResult.script)); - throw new Error('New bytecode optimisation is not backwards compatible, please report this issue to the CashScript team'); - } - const debug = { bytecode: binToHex(scriptToBytecode(optimisationResult.script)), sourceMap: generateSourceMap(optimisationResult.locationData), diff --git a/packages/cashc/test/cashproof/0.1.2=0.2.0.equiv b/packages/cashc/test/cashproof/0.1.2=0.2.0.equiv deleted file mode 100644 index 99d2bf7f..00000000 --- a/packages/cashc/test/cashproof/0.1.2=0.2.0.equiv +++ /dev/null @@ -1,111 +0,0 @@ -# This file includes several examples CashScript contracts as compiled with 0.1.2 and 0.2.0 -# to verify their equivalence. These Scripts contain the exact output as compiled by Cashc, -# except for the following changes: -# * "Default case" has been added to the 0.1.2 bytecode, as this was a vulnerability fix in 0.2.0 -# but we are only interested in the optimisation equivalence, so we simulate this fix for 0.1.2 -# * Final OP_1/0 has been changed to OP_TRUE/FALSE to play nice with CashProof type checking -# * Raw data has been prepended with 0x or changed to integer representation -# (CashScript hex-encodes numbers, CashProof expects decimal numbers, -# and assumes hex-encoded data is of the raw bytes type). - -!full_script=True; - -# P2PKH -# 12 opcount, 17 bytes -OP_1 OP_PICK OP_HASH160 OP_1 OP_PICK OP_EQUAL OP_VERIFY OP_2 OP_PICK OP_2 OP_PICK OP_CHECKSIG OP_VERIFY OP_DROP OP_DROP OP_DROP OP_TRUE -<=> -# 4 opcount, 4 bytes -OP_OVER OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG -# optimisation: opcount ~67%, bytes ~76% -; - -# TransferWithTimeout -# 31 opcount, 43 bytes -OP_3 OP_PICK OP_0 OP_NUMEQUAL OP_IF OP_4 OP_PICK OP_2 OP_PICK OP_CHECKSIG OP_VERIFY OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_TRUE OP_ELSE OP_3 OP_PICK OP_1 OP_NUMEQUAL OP_IF OP_4 OP_PICK OP_1 OP_PICK OP_CHECKSIG OP_VERIFY OP_2 OP_PICK OP_CHECKLOCKTIMEVERIFY OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_TRUE OP_ELSE OP_FALSE OP_ENDIF OP_ENDIF -<=> -# 23 opcount, 31 bytes -OP_3 OP_PICK OP_0 OP_NUMEQUAL OP_IF OP_4 OP_ROLL OP_ROT OP_CHECKSIG OP_NIP OP_NIP OP_NIP OP_ELSE OP_3 OP_ROLL OP_1 OP_NUMEQUAL OP_IF OP_3 OP_ROLL OP_SWAP OP_CHECKSIGVERIFY OP_SWAP OP_CHECKLOCKTIMEVERIFY OP_DROP OP_TRUE OP_NIP OP_ELSE OP_FALSE OP_ENDIF OP_ENDIF -# optimisation: opcount ~26%, bytes ~28% -; - -# HodlVault -# 37 opcount, 52 bytes -OP_6 OP_PICK OP_4 OP_SPLIT OP_DROP OP_BIN2NUM OP_7 OP_PICK OP_4 OP_SPLIT OP_NIP OP_BIN2NUM OP_1 OP_PICK OP_5 OP_PICK OP_GREATERTHANOREQUAL OP_VERIFY OP_1 OP_PICK OP_CHECKLOCKTIMEVERIFY OP_DROP OP_0 OP_PICK OP_6 OP_PICK OP_GREATERTHANOREQUAL OP_VERIFY OP_7 OP_PICK OP_9 OP_PICK OP_5 OP_PICK OP_CHECKDATASIG OP_VERIFY OP_6 OP_PICK OP_3 OP_PICK OP_CHECKSIG OP_VERIFY OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_TRUE -<=> -# 23 opcount, 32 bytes -OP_6 OP_PICK OP_4 OP_SPLIT OP_DROP OP_BIN2NUM OP_7 OP_PICK OP_4 OP_SPLIT OP_NIP OP_BIN2NUM OP_OVER OP_5 OP_ROLL OP_GREATERTHANOREQUAL OP_VERIFY OP_SWAP OP_CHECKLOCKTIMEVERIFY OP_DROP OP_3 OP_ROLL OP_GREATERTHANOREQUAL OP_VERIFY OP_3 OP_ROLL OP_4 OP_ROLL OP_3 OP_ROLL OP_CHECKDATASIGVERIFY OP_CHECKSIG -# optimisation: opcount ~38%, bytes ~38% -; - -# # Doesn't work yet -- CashProof doesn't fully support CMS yet -# # 2 of 3 Multisig -# # 12 opcount, 21 bytes -# OP_0 OP_3 OP_PICK OP_5 OP_PICK OP_2 OP_3 OP_PICK OP_5 OP_PICK OP_7 OP_PICK OP_3 OP_CHECKMULTISIG OP_VERIFY OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_TRUE -# <=> -# # 6 opcount, 12 bytes -# OP_0 OP_3 OP_ROLL OP_4 OP_ROLL OP_2 OP_3 OP_ROLL OP_2ROT OP_SWAP OP_3 OP_CHECKMULTISIG -# optimisation: opcount 50%, bytes ~43% -# ; - -# # Does prove, but this is very slow, so it's commented out -# # Zero-conf forfeits -# # 60 opcount, 83 bytes -# OP_2 OP_PICK OP_0 OP_NUMEQUAL OP_IF OP_3 OP_PICK OP_HASH160 OP_2 OP_PICK OP_EQUAL OP_VERIFY OP_4 OP_PICK OP_4 OP_PICK OP_CHECKSIG OP_VERIFY OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_TRUE OP_ELSE OP_2 OP_PICK OP_1 OP_NUMEQUAL OP_IF OP_7 OP_PICK OP_HASH160 OP_1 OP_PICK OP_EQUAL OP_VERIFY OP_4 OP_PICK OP_7 OP_PICK OP_EQUAL OP_NOT OP_VERIFY OP_3 OP_PICK OP_SIZE OP_1 OP_SUB OP_SPLIT OP_DROP OP_5 OP_PICK OP_9 OP_PICK OP_CHECKDATASIG OP_VERIFY OP_5 OP_PICK OP_SIZE OP_1 OP_SUB OP_SPLIT OP_DROP OP_7 OP_PICK OP_9 OP_PICK OP_CHECKDATASIG OP_VERIFY OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_TRUE OP_ELSE OP_FALSE OP_ENDIF OP_ENDIF -# <=> -# # 43 opcount, 52 bytes -# OP_2 OP_PICK OP_0 OP_NUMEQUAL OP_IF OP_3 OP_PICK OP_HASH160 OP_ROT OP_EQUALVERIFY OP_2SWAP OP_CHECKSIG OP_NIP OP_NIP OP_ELSE OP_ROT OP_1 OP_NUMEQUAL OP_IF OP_6 OP_PICK OP_HASH160 OP_EQUALVERIFY OP_2 OP_PICK OP_5 OP_PICK OP_EQUAL OP_NOT OP_VERIFY OP_SWAP OP_SIZE OP_1SUB OP_SPLIT OP_DROP OP_ROT OP_5 OP_PICK OP_CHECKDATASIGVERIFY OP_SWAP OP_SIZE OP_1SUB OP_SPLIT OP_DROP OP_2SWAP OP_SWAP OP_CHECKDATASIG OP_NIP OP_ELSE OP_FALSE OP_ENDIF OP_ENDIF -# optimisation: opcount ~28%, bytes ~37% -# ; - -# HTLC -# 39 opcount, 53 bytes -OP_4 OP_PICK OP_0 OP_NUMEQUAL OP_IF OP_5 OP_PICK OP_SHA256 OP_4 OP_PICK OP_EQUAL OP_VERIFY OP_6 OP_PICK OP_2 OP_PICK OP_CHECKSIG OP_VERIFY OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_TRUE OP_ELSE OP_4 OP_PICK OP_1 OP_NUMEQUAL OP_IF OP_2 OP_PICK OP_CHECKLOCKTIMEVERIFY OP_DROP OP_5 OP_PICK OP_1 OP_PICK OP_CHECKSIG OP_VERIFY OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_TRUE OP_ELSE OP_FALSE OP_ENDIF OP_ENDIF -<=> -# 28 opcount, 37 bytes -OP_4 OP_PICK OP_0 OP_NUMEQUAL OP_IF OP_5 OP_ROLL OP_SHA256 OP_4 OP_ROLL OP_EQUALVERIFY OP_4 OP_ROLL OP_ROT OP_CHECKSIG OP_NIP OP_NIP OP_NIP OP_ELSE OP_4 OP_ROLL OP_1 OP_NUMEQUAL OP_IF OP_ROT OP_CHECKLOCKTIMEVERIFY OP_DROP OP_3 OP_ROLL OP_SWAP OP_CHECKSIG OP_NIP OP_NIP OP_ELSE OP_FALSE OP_ENDIF OP_ENDIF -# optimisation: opcount ~28%, bytes ~30% -; - -# Local.bitcoin.com -# 86 opcount, 128 bytes -OP_0 OP_8 OP_NUM2BIN OP_0 OP_8 OP_NUM2BIN OP_10 OP_PICK OP_1 OP_NUMEQUAL OP_IF OP_3 OP_PICK OP_2 OP_ROLL OP_DROP OP_SWAP OP_2 OP_PICK OP_1 OP_ROLL OP_DROP OP_ELSE OP_10 OP_PICK OP_2 OP_NUMEQUAL OP_IF OP_3 OP_PICK OP_2 OP_ROLL OP_DROP OP_SWAP OP_4 OP_PICK OP_1 OP_ROLL OP_DROP OP_ELSE OP_10 OP_PICK OP_3 OP_NUMEQUAL OP_IF OP_2 OP_PICK OP_2 OP_ROLL OP_DROP OP_SWAP OP_3 OP_PICK OP_1 OP_ROLL OP_DROP OP_ELSE OP_10 OP_PICK OP_4 OP_NUMEQUAL OP_IF OP_2 OP_PICK OP_2 OP_ROLL OP_DROP OP_SWAP OP_4 OP_PICK OP_1 OP_ROLL OP_DROP OP_ELSE OP_FALSE OP_VERIFY OP_ENDIF OP_ENDIF OP_ENDIF OP_ENDIF OP_9 OP_PICK OP_HASH160 OP_1 OP_PICK OP_EQUAL OP_VERIFY OP_7 OP_PICK OP_HASH160 OP_2 OP_PICK OP_EQUAL OP_VERIFY OP_5 OP_PICK OP_11 OP_PICK OP_8 OP_NUM2BIN OP_CAT OP_9 OP_PICK OP_1 OP_PICK OP_12 OP_PICK OP_CHECKDATASIG OP_VERIFY OP_7 OP_PICK OP_9 OP_PICK OP_CHECKSIG OP_VERIFY OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_TRUE -<=> -# 60 opcount, 86 bytes -OP_0 OP_8 OP_NUM2BIN OP_0 OP_8 OP_NUM2BIN OP_10 OP_PICK OP_1 OP_NUMEQUAL OP_IF OP_2DROP OP_2DUP OP_ELSE OP_10 OP_PICK OP_2 OP_NUMEQUAL OP_IF OP_NIP OP_2 OP_PICK OP_SWAP OP_4 OP_PICK OP_NIP OP_ELSE OP_10 OP_PICK OP_3 OP_NUMEQUAL OP_IF OP_NIP OP_OVER OP_SWAP OP_3 OP_PICK OP_NIP OP_ELSE OP_10 OP_PICK OP_4 OP_NUMEQUAL OP_IF OP_NIP OP_OVER OP_SWAP OP_4 OP_PICK OP_NIP OP_ELSE OP_FALSE OP_VERIFY OP_ENDIF OP_ENDIF OP_ENDIF OP_ENDIF OP_9 OP_PICK OP_HASH160 OP_EQUALVERIFY OP_6 OP_PICK OP_HASH160 OP_EQUALVERIFY OP_3 OP_ROLL OP_8 OP_ROLL OP_8 OP_NUM2BIN OP_CAT OP_6 OP_ROLL OP_SWAP OP_7 OP_ROLL OP_CHECKDATASIGVERIFY OP_3 OP_ROLL OP_4 OP_ROLL OP_CHECKSIG OP_NIP OP_NIP OP_NIP -# optimisation: opcount ~33%, bytes ~33% -; - -# Last Will -# 139 opcount, 207 bytes -OP_3 OP_PICK OP_0 OP_NUMEQUAL OP_IF OP_4 OP_PICK OP_SHA256 OP_RIPEMD160 OP_1 OP_PICK OP_EQUAL OP_VERIFY OP_5 OP_PICK OP_5 OP_PICK OP_CHECKSIG OP_VERIFY OP_6 OP_PICK OP_8 OP_PICK OP_CAT OP_9 OP_PICK OP_CAT OP_10 OP_PICK OP_CAT OP_11 OP_PICK OP_CAT OP_12 OP_PICK OP_CAT OP_13 OP_PICK OP_CAT OP_6 OP_PICK OP_SIZE OP_1 OP_SUB OP_SPLIT OP_DROP OP_1 OP_PICK OP_SHA256 OP_7 OP_PICK OP_CHECKDATASIG OP_VERIFY 1000 OP_11 OP_PICK OP_BIN2NUM OP_1 OP_PICK OP_SUB OP_8 OP_NUM2BIN 0x87 0xa9 0x14 0x17 OP_15 OP_PICK OP_1 OP_SPLIT OP_NIP 436800 OP_CHECKSEQUENCEVERIFY OP_DROP OP_14 OP_PICK OP_BIN2NUM OP_2 OP_GREATERTHANOREQUAL OP_VERIFY OP_5 OP_PICK OP_2 OP_PICK OP_CAT OP_4 OP_PICK OP_CAT OP_3 OP_PICK OP_CAT OP_1 OP_PICK OP_HASH160 OP_CAT OP_5 OP_PICK OP_CAT OP_HASH256 20 OP_PICK OP_EQUAL OP_VERIFY OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_TRUE OP_ELSE OP_3 OP_PICK OP_1 OP_NUMEQUAL OP_IF OP_4 OP_PICK OP_HASH160 OP_2 OP_PICK OP_EQUAL OP_VERIFY OP_5 OP_PICK OP_5 OP_PICK OP_CHECKSIG OP_VERIFY OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_TRUE OP_ELSE OP_3 OP_PICK OP_2 OP_NUMEQUAL OP_IF 15552000 OP_CHECKSEQUENCEVERIFY OP_DROP OP_4 OP_PICK OP_HASH160 OP_3 OP_PICK OP_EQUAL OP_VERIFY OP_5 OP_PICK OP_5 OP_PICK OP_CHECKSIG OP_VERIFY OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_TRUE OP_ELSE OP_FALSE OP_ENDIF OP_ENDIF OP_ENDIF -<=> -# 102 opcount, 155 bytes -OP_3 OP_PICK OP_0 OP_NUMEQUAL OP_IF OP_4 OP_PICK OP_HASH160 OP_EQUALVERIFY OP_4 OP_PICK OP_4 OP_PICK OP_CHECKSIGVERIFY OP_5 OP_PICK OP_7 OP_ROLL OP_CAT OP_7 OP_PICK OP_CAT OP_8 OP_PICK OP_CAT OP_9 OP_ROLL OP_CAT OP_9 OP_PICK OP_CAT OP_10 OP_ROLL OP_CAT OP_5 OP_ROLL OP_SIZE OP_1SUB OP_SPLIT OP_DROP OP_SWAP OP_SHA256 OP_5 OP_ROLL OP_CHECKDATASIGVERIFY 1000 OP_6 OP_ROLL OP_BIN2NUM OP_SWAP OP_SUB OP_8 OP_NUM2BIN 0x87 0xa9 0x14 0x17 OP_9 OP_ROLL OP_1 OP_SPLIT OP_NIP 436800 OP_CHECKSEQUENCEVERIFY OP_DROP OP_9 OP_ROLL OP_BIN2NUM OP_2 OP_GREATERTHANOREQUAL OP_VERIFY OP_5 OP_ROLL OP_ROT OP_CAT OP_3 OP_ROLL OP_CAT OP_ROT OP_CAT OP_SWAP OP_HASH160 OP_CAT OP_SWAP OP_CAT OP_HASH256 OP_4 OP_ROLL OP_EQUAL OP_NIP OP_NIP OP_NIP OP_ELSE OP_3 OP_PICK OP_1 OP_NUMEQUAL OP_IF OP_4 OP_PICK OP_HASH160 OP_ROT OP_EQUALVERIFY OP_4 OP_ROLL OP_4 OP_ROLL OP_CHECKSIG OP_NIP OP_NIP OP_NIP OP_ELSE OP_3 OP_ROLL OP_2 OP_NUMEQUAL OP_IF 15552000 OP_CHECKSEQUENCEVERIFY OP_DROP OP_3 OP_PICK OP_HASH160 OP_3 OP_ROLL OP_EQUALVERIFY OP_2SWAP OP_CHECKSIG OP_NIP OP_NIP OP_ELSE OP_FALSE OP_ENDIF OP_ENDIF OP_ENDIF -# optimisation: opcount ~27%, bytes ~25% -; - -# Mecenas -# 177 opcount, 260 bytes -OP_3 OP_PICK OP_0 OP_NUMEQUAL OP_IF OP_6 OP_PICK OP_SIZE OP_NIP OP_4 OP_NUMEQUAL OP_VERIFY OP_7 OP_PICK OP_SIZE OP_NIP 100 OP_NUMEQUAL OP_VERIFY OP_9 OP_PICK OP_SIZE OP_NIP OP_8 OP_NUMEQUAL OP_VERIFY OP_10 OP_PICK OP_SIZE OP_NIP OP_4 OP_NUMEQUAL OP_VERIFY OP_10 OP_PICK OP_SIZE OP_NIP 32 OP_NUMEQUAL OP_VERIFY OP_12 OP_PICK OP_SIZE OP_NIP OP_8 OP_NUMEQUAL OP_VERIFY OP_5 OP_PICK OP_5 OP_PICK OP_CHECKSIG OP_VERIFY OP_6 OP_PICK OP_8 OP_PICK OP_CAT OP_9 OP_PICK OP_CAT OP_10 OP_PICK OP_CAT OP_11 OP_PICK OP_CAT OP_12 OP_PICK OP_CAT OP_13 OP_PICK OP_CAT OP_6 OP_PICK OP_SIZE OP_1 OP_SUB OP_SPLIT OP_DROP OP_1 OP_PICK OP_SHA256 OP_7 OP_PICK OP_CHECKDATASIG OP_VERIFY 1000 OP_4 OP_PICK OP_8 OP_NUM2BIN OP_12 OP_PICK OP_BIN2NUM OP_6 OP_PICK OP_SUB OP_2 OP_PICK OP_SUB OP_8 OP_NUM2BIN 0x76 0x87 0xa9 0x14 0x17 0x19 0x88 0xac 20 OP_PICK OP_3 OP_SPLIT OP_NIP 2700000 OP_CHECKSEQUENCEVERIFY OP_DROP 19 OP_PICK OP_BIN2NUM OP_2 OP_GREATERTHANOREQUAL OP_VERIFY OP_9 OP_PICK OP_5 OP_PICK OP_CAT OP_7 OP_PICK OP_CAT OP_6 OP_PICK OP_CAT OP_1 OP_PICK OP_HASH160 OP_CAT OP_8 OP_PICK OP_CAT OP_11 OP_PICK OP_5 OP_PICK OP_CAT OP_10 OP_PICK OP_CAT OP_8 OP_PICK OP_CAT OP_7 OP_PICK OP_CAT OP_15 OP_PICK OP_CAT OP_4 OP_PICK OP_CAT OP_3 OP_PICK OP_CAT OP_1 OP_PICK OP_1 OP_PICK OP_CAT OP_HASH256 27 OP_PICK OP_SHA256 OP_EQUAL OP_VERIFY OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_TRUE OP_ELSE OP_3 OP_PICK OP_1 OP_NUMEQUAL OP_IF OP_4 OP_PICK OP_HASH160 OP_2 OP_PICK OP_EQUAL OP_VERIFY OP_5 OP_PICK OP_5 OP_PICK OP_CHECKSIG OP_VERIFY OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_DROP OP_TRUE OP_ELSE OP_FALSE OP_ENDIF OP_ENDIF -<=> -# 131 opcount, 207 bytes -OP_3 OP_PICK OP_0 OP_NUMEQUAL OP_IF OP_6 OP_PICK OP_SIZE OP_NIP OP_4 OP_NUMEQUALVERIFY OP_7 OP_PICK OP_SIZE OP_NIP 100 OP_NUMEQUALVERIFY OP_9 OP_PICK OP_SIZE OP_NIP OP_8 OP_NUMEQUALVERIFY OP_10 OP_PICK OP_SIZE OP_NIP OP_4 OP_NUMEQUALVERIFY OP_10 OP_PICK OP_SIZE OP_NIP 32 OP_NUMEQUALVERIFY OP_12 OP_PICK OP_SIZE OP_NIP OP_8 OP_NUMEQUALVERIFY OP_5 OP_PICK OP_5 OP_PICK OP_CHECKSIGVERIFY OP_6 OP_PICK OP_8 OP_ROLL OP_CAT OP_8 OP_PICK OP_CAT OP_9 OP_PICK OP_CAT OP_10 OP_ROLL OP_CAT OP_10 OP_PICK OP_CAT OP_11 OP_ROLL OP_CAT OP_6 OP_ROLL OP_SIZE OP_1SUB OP_SPLIT OP_DROP OP_SWAP OP_SHA256 OP_6 OP_ROLL OP_CHECKDATASIGVERIFY 1000 OP_3 OP_PICK OP_8 OP_NUM2BIN OP_8 OP_ROLL OP_BIN2NUM OP_5 OP_ROLL OP_SUB OP_ROT OP_SUB OP_8 OP_NUM2BIN 0x76 0x87 0xa9 0x14 0x17 0x19 0x88 0xac OP_14 OP_ROLL OP_3 OP_SPLIT OP_NIP 2700000 OP_CHECKSEQUENCEVERIFY OP_DROP OP_14 OP_ROLL OP_BIN2NUM OP_2 OP_GREATERTHANOREQUAL OP_VERIFY OP_9 OP_ROLL OP_5 OP_ROLL OP_CAT OP_6 OP_PICK OP_CAT OP_5 OP_PICK OP_CAT OP_SWAP OP_HASH160 OP_CAT OP_6 OP_ROLL OP_CAT OP_7 OP_ROLL OP_4 OP_ROLL OP_CAT OP_6 OP_ROLL OP_CAT OP_5 OP_ROLL OP_CAT OP_4 OP_ROLL OP_CAT OP_4 OP_ROLL OP_CAT OP_3 OP_ROLL OP_CAT OP_ROT OP_CAT OP_CAT OP_HASH256 OP_3 OP_ROLL OP_SHA256 OP_EQUAL OP_NIP OP_NIP OP_ELSE OP_3 OP_ROLL OP_1 OP_NUMEQUAL OP_IF OP_3 OP_PICK OP_HASH160 OP_ROT OP_EQUALVERIFY OP_2SWAP OP_CHECKSIG OP_NIP OP_NIP OP_ELSE OP_FALSE OP_ENDIF OP_ENDIF -# optimisation: opcount ~26%, bytes ~20% -; - -# Avg unoptimised opcount: 593 / 9 ≈ 65.89 -# Avg unoptimised bytes: 864 / 9 = 96 - -# Avg optimised opcount: 420 / 9 ≈ 46.67 -# Avg optimised bytes: 616 / 9 ≈ 68.44 - -# Avg opcount optimisation: (56.75 - 39.75) / 56.75 ≈ 29% -# Avg bytes optimisation: (96 - 68.44) / 96 ≈ 29% - -# Min optimisation (Mecenas): -# opcount: 177 -> 131 (~26%) -# bytes: 250 -> 207 (~20%) - -# Max optimisation (P2PKH): -# opcount: 12 -> 4 (~67%) -# bytes: 17 -> 4 (~76%) diff --git a/packages/cashc/test/cashproof/0.3.3=0.4.0.equiv b/packages/cashc/test/cashproof/0.3.3=0.4.0.equiv deleted file mode 100644 index a6569563..00000000 --- a/packages/cashc/test/cashproof/0.3.3=0.4.0.equiv +++ /dev/null @@ -1,32 +0,0 @@ -!full_script=True; - - -# THIS CASHPROOF FAILS DUE TO THE NUMEQUALVERIFY OPTIMISATION MAKING THE IF-STATEMENTS -# NOT EQUIVALENT. I'M STILL COMMITTING THIS FILE FOR REFERENCE. - -# TransferWithTimeout -# 22 opcount, 31 bytes -OP_3 OP_PICK OP_0 OP_NUMEQUAL OP_IF OP_4 OP_ROLL OP_ROT OP_CHECKSIG OP_NIP OP_NIP OP_NIP OP_ELSE OP_3 OP_ROLL OP_1 OP_NUMEQUAL OP_IF OP_3 OP_ROLL OP_SWAP OP_CHECKSIGVERIFY OP_SWAP OP_CHECKLOCKTIMEVERIFY OP_2DROP OP_TRUE OP_ELSE OP_FALSE OP_ENDIF OP_ENDIF -<=> -# 19 opcount, 26 bytes -OP_3 OP_PICK OP_0 OP_NUMEQUAL OP_IF OP_4 OP_ROLL OP_ROT OP_CHECKSIG OP_NIP OP_NIP OP_NIP OP_ELSE OP_3 OP_ROLL OP_1 OP_NUMEQUALVERIFY OP_3 OP_ROLL OP_SWAP OP_CHECKSIGVERIFY OP_SWAP OP_CHECKLOCKTIMEVERIFY OP_2DROP OP_TRUE OP_ENDIF -# optimisation: opcount ~14%, bytes ~16% -; - -# HTLC -# 28 opcount, 37 bytes -OP_4 OP_PICK OP_0 OP_NUMEQUAL OP_IF OP_5 OP_ROLL OP_SHA256 OP_4 OP_ROLL OP_EQUALVERIFY OP_4 OP_ROLL OP_ROT OP_CHECKSIG OP_NIP OP_NIP OP_NIP OP_ELSE OP_4 OP_ROLL OP_1 OP_NUMEQUAL OP_IF OP_ROT OP_CHECKLOCKTIMEVERIFY OP_DROP OP_3 OP_ROLL OP_SWAP OP_CHECKSIG OP_NIP OP_NIP OP_ELSE OP_FALSE OP_ENDIF OP_ENDIF -<=> -# 25 opcount, 33 bytes -OP_4 OP_PICK OP_0 OP_NUMEQUAL OP_IF OP_5 OP_ROLL OP_SHA256 OP_4 OP_ROLL OP_EQUALVERIFY OP_4 OP_ROLL OP_ROT OP_CHECKSIG OP_NIP OP_NIP OP_NIP OP_ELSE OP_4 OP_ROLL OP_1 OP_NUMEQUALVERIFY OP_ROT OP_CHECKLOCKTIMEVERIFY OP_DROP OP_3 OP_ROLL OP_SWAP OP_CHECKSIG OP_NIP OP_NIP OP_ENDIF -# optimisation: opcount ~11%, bytes ~11% -; - -# Last Will -# 87 opcount, 131 bytes -OP_3 OP_PICK OP_0 OP_NUMEQUAL OP_IF 15552000 OP_CHECKSEQUENCEVERIFY OP_DROP OP_4 OP_PICK OP_HASH160 OP_3 OP_ROLL OP_EQUALVERIFY OP_4 OP_ROLL OP_4 OP_ROLL OP_CHECKSIG OP_NIP OP_NIP OP_NIP OP_ELSE OP_3 OP_PICK OP_1 OP_NUMEQUAL OP_IF OP_4 OP_PICK OP_HASH160 OP_ROT OP_EQUALVERIFY OP_4 OP_ROLL OP_4 OP_ROLL OP_CHECKSIG OP_NIP OP_NIP OP_NIP OP_ELSE OP_3 OP_ROLL OP_2 OP_NUMEQUAL OP_IF OP_3 OP_PICK 105 OP_SPLIT OP_NIP OP_SIZE 52 OP_SUB OP_SPLIT OP_8 OP_SPLIT OP_4 OP_SPLIT OP_NIP 32 OP_SPLIT OP_DROP OP_7 OP_PICK OP_HASH160 OP_4 OP_ROLL OP_EQUALVERIFY OP_7 OP_ROLL OP_7 OP_ROLL OP_2DUP OP_SWAP OP_SIZE OP_1SUB OP_SPLIT OP_DROP OP_9 OP_ROLL OP_SHA256 OP_ROT OP_CHECKDATASIGVERIFY OP_CHECKSIGVERIFY 1000 OP_ROT OP_BIN2NUM OP_SWAP OP_SUB OP_8 OP_NUM2BIN 0x17a914 OP_CAT OP_ROT OP_HASH160 OP_CAT 0x87 OP_CAT OP_HASH256 OP_EQUAL OP_NIP OP_NIP OP_ELSE OP_FALSE OP_ENDIF OP_ENDIF OP_ENDIF -<=> -# 83 opcount, 124 -OP_3 OP_PICK OP_0 OP_NUMEQUAL OP_IF 15552000 OP_CHECKSEQUENCEVERIFY OP_DROP OP_4 OP_PICK OP_HASH160 OP_3 OP_ROLL OP_EQUALVERIFY OP_4 OP_ROLL OP_4 OP_ROLL OP_CHECKSIG OP_NIP OP_NIP OP_NIP OP_ELSE OP_3 OP_PICK OP_1 OP_NUMEQUAL OP_IF OP_4 OP_PICK OP_HASH160 OP_ROT OP_EQUALVERIFY OP_4 OP_ROLL OP_4 OP_ROLL OP_CHECKSIG OP_NIP OP_NIP OP_NIP OP_ELSE OP_3 OP_ROLL OP_2 OP_NUMEQUALVERIFY OP_3 OP_PICK 105 OP_SPLIT OP_NIP OP_SIZE 52 OP_SUB OP_SPLIT OP_8 OP_SPLIT OP_4 OP_SPLIT OP_NIP 32 OP_SPLIT OP_DROP OP_7 OP_PICK OP_HASH160 OP_4 OP_ROLL OP_EQUALVERIFY OP_7 OP_ROLL OP_7 OP_ROLL OP_2DUP OP_SWAP OP_SIZE OP_1SUB OP_SPLIT OP_DROP OP_9 OP_ROLL OP_SHA256 OP_ROT OP_CHECKDATASIGVERIFY OP_CHECKSIGVERIFY 1000 OP_ROT OP_BIN2NUM OP_SWAP OP_SUB OP_8 OP_NUM2BIN 0x17a914 OP_CAT OP_ROT OP_HASH160 OP_CAT 0x87 OP_CAT OP_HASH256 OP_EQUAL OP_NIP OP_NIP OP_ENDIF OP_ENDIF -# optimisation: opcount ~5%, bytes ~5% -; diff --git a/packages/cashc/test/cashproof/slice.equiv b/packages/cashc/test/cashproof/slice.equiv deleted file mode 100644 index e16364cb..00000000 --- a/packages/cashc/test/cashproof/slice.equiv +++ /dev/null @@ -1,14 +0,0 @@ -!full_script=True; - -# We are unable to run cashproof any more due to Python ecosystem issues, but we're including this file for reference. - -# x.slice(10, 25) & x.split(25)[0].split(10)[1] -25 OP_SPLIT OP_DROP OP_10 OP_SPLIT OP_NIP -<=> -# x.split(10)[1].split(15)[0] -OP_10 OP_SPLIT OP_NIP OP_15 OP_SPLIT OP_DROP -; - -# Slice optimisation -OP_0 OP_SPLIT OP_NIP <=> ; -OP_SIZE OP_SPLIT OP_DROP <=> ; diff --git a/packages/utils/src/cashproof-optimisations.ts b/packages/utils/src/cashproof-optimisations.ts deleted file mode 100644 index ddb5cf13..00000000 --- a/packages/utils/src/cashproof-optimisations.ts +++ /dev/null @@ -1,139 +0,0 @@ -export default ` -# This file can be run with CashProof to prove that the optimisations preserve exact functionality -# This includes most of CashScript's bytecode optimisations, although some are incompatible with CashProof - -# Hardcoded arithmetic -# OP_NOT OP_IF <=> OP_NOTIF; -OP_1 OP_ADD <=> OP_1ADD; -OP_1 OP_SUB <=> OP_1SUB; -OP_1 OP_NEGATE <=> OP_1NEGATE; -OP_0 OP_NUMEQUAL OP_NOT <=> OP_0NOTEQUAL; -OP_NUMEQUAL OP_NOT <=> OP_NUMNOTEQUAL; -OP_SHA256 OP_SHA256 <=> OP_HASH256; -OP_SHA256 OP_RIPEMD160 <=> OP_HASH160; - -# Hardcoded stack ops -OP_2 OP_PICK OP_1 OP_PICK OP_3 OP_PICK <=> OP_3DUP OP_SWAP; -OP_2 OP_PICK OP_2 OP_PICK OP_2 OP_PICK <=> OP_3DUP; - -OP_0 OP_PICK OP_2 OP_PICK <=> OP_2DUP OP_SWAP; -OP_2 OP_PICK OP_4 OP_PICK <=> OP_2OVER OP_SWAP; -OP_3 OP_PICK OP_3 OP_PICK <=> OP_2OVER; - -OP_2 OP_ROLL OP_3 OP_ROLL <=> OP_2SWAP OP_SWAP; -OP_3 OP_ROLL OP_3 OP_ROLL <=> OP_2SWAP; -OP_4 OP_ROLL OP_5 OP_ROLL <=> OP_2ROT OP_SWAP; -OP_5 OP_ROLL OP_5 OP_ROLL <=> OP_2ROT; - -OP_0 OP_PICK <=> OP_DUP; -OP_1 OP_PICK <=> OP_OVER; -OP_0 OP_ROLL <=> ; -OP_1 OP_ROLL <=> OP_SWAP; -OP_2 OP_ROLL <=> OP_ROT; -OP_DROP OP_DROP <=> OP_2DROP; - -# Secondary effects -OP_DUP OP_SWAP <=> OP_DUP; -OP_SWAP OP_SWAP <=> ; -OP_2SWAP OP_2SWAP <=> ; -OP_ROT OP_ROT OP_ROT <=> ; -OP_2ROT OP_2ROT OP_2ROT <=> ; -OP_OVER OP_OVER <=> OP_2DUP; -OP_DUP OP_DROP <=> ; -OP_DUP OP_NIP <=> ; - -# Enabling secondary effects -OP_DUP OP_OVER <=> OP_DUP OP_DUP; - -# Merge OP_VERIFY -OP_EQUAL OP_VERIFY <=> OP_EQUALVERIFY; -OP_NUMEQUAL OP_VERIFY <=> OP_NUMEQUALVERIFY; -OP_CHECKSIG OP_VERIFY <=> OP_CHECKSIGVERIFY; -# OP_CHECKMULTISIG OP_VERIFY <=> OP_CHECKMULTISIGVERIFY; -OP_CHECKDATASIG OP_VERIFY <=> OP_CHECKDATASIGVERIFY; - -# Remove/replace extraneous OP_SWAP -# OP_SWAP OP_AND <=> OP_AND; -# OP_SWAP OP_OR <=> OP_OR; -# OP_SWAP OP_XOR <=> OP_XOR; -OP_SWAP OP_ADD <=> OP_ADD; -OP_SWAP OP_MUL <=> OP_MUL; -OP_SWAP OP_EQUAL <=> OP_EQUAL; -OP_SWAP OP_NUMEQUAL <=> OP_NUMEQUAL; -OP_SWAP OP_NUMNOTEQUAL <=> OP_NUMNOTEQUAL; -OP_SWAP OP_GREATERTHANOREQUAL <=> OP_LESSTHANOREQUAL; -OP_SWAP OP_LESSTHANOREQUAL <=> OP_GREATERTHANOREQUAL; -OP_SWAP OP_GREATERTHAN <=> OP_LESSTHAN; -OP_SWAP OP_LESSTHAN <=> OP_GREATERTHAN; -OP_SWAP OP_DROP <=> OP_NIP; -OP_SWAP OP_NIP <=> OP_DROP; - -# Remove/replace extraneous OP_DUP -# OP_DUP OP_AND <=> ; -# OP_DUP OP_OR <=> ; -OP_DUP OP_DROP <=> ; -OP_DUP OP_NIP <=> ; - -# Random optimisations (don't know what I'm targeting with this) -OP_2DUP OP_DROP <=> OP_OVER; -OP_2DUP OP_NIP <=> OP_DUP; -OP_CAT OP_DROP <=> OP_2DROP; -OP_NIP OP_DROP <=> OP_2DROP; - -# Far-fetched stuff -OP_DUP OP_ROT OP_SWAP OP_DROP <=> OP_SWAP; -OP_OVER OP_ROT OP_SWAP OP_DROP <=> OP_SWAP; -OP_2 OP_PICK OP_ROT OP_SWAP OP_DROP <=> OP_SWAP; -OP_3 OP_PICK OP_ROT OP_SWAP OP_DROP <=> OP_SWAP; -OP_4 OP_PICK OP_ROT OP_SWAP OP_DROP <=> OP_SWAP; -OP_5 OP_PICK OP_ROT OP_SWAP OP_DROP <=> OP_SWAP; -OP_6 OP_PICK OP_ROT OP_SWAP OP_DROP <=> OP_SWAP; -OP_7 OP_PICK OP_ROT OP_SWAP OP_DROP <=> OP_SWAP; -OP_8 OP_PICK OP_ROT OP_SWAP OP_DROP <=> OP_SWAP; -OP_9 OP_PICK OP_ROT OP_SWAP OP_DROP <=> OP_SWAP; -OP_10 OP_PICK OP_ROT OP_SWAP OP_DROP <=> OP_SWAP; -OP_11 OP_PICK OP_ROT OP_SWAP OP_DROP <=> OP_SWAP; -OP_12 OP_PICK OP_ROT OP_SWAP OP_DROP <=> OP_SWAP; -OP_13 OP_PICK OP_ROT OP_SWAP OP_DROP <=> OP_SWAP; -OP_14 OP_PICK OP_ROT OP_SWAP OP_DROP <=> OP_SWAP; -OP_15 OP_PICK OP_ROT OP_SWAP OP_DROP <=> OP_SWAP; -OP_16 OP_PICK OP_ROT OP_SWAP OP_DROP <=> OP_SWAP; - -OP_DUP OP_ROT OP_DROP <=> OP_NIP OP_DUP; -OP_OVER OP_ROT OP_DROP <=> OP_SWAP; -OP_2 OP_PICK OP_ROT OP_DROP <=> OP_NIP OP_OVER; - -OP_0 OP_NIP <=> OP_DROP OP_0; -OP_1 OP_NIP <=> OP_DROP OP_1; -OP_2 OP_NIP <=> OP_DROP OP_2; -OP_3 OP_NIP <=> OP_DROP OP_3; -OP_4 OP_NIP <=> OP_DROP OP_4; -OP_5 OP_NIP <=> OP_DROP OP_5; -OP_6 OP_NIP <=> OP_DROP OP_6; -OP_7 OP_NIP <=> OP_DROP OP_7; -OP_8 OP_NIP <=> OP_DROP OP_8; -OP_9 OP_NIP <=> OP_DROP OP_9; -OP_10 OP_NIP <=> OP_DROP OP_10; -OP_11 OP_NIP <=> OP_DROP OP_11; -OP_12 OP_NIP <=> OP_DROP OP_12; -OP_13 OP_NIP <=> OP_DROP OP_13; -OP_14 OP_NIP <=> OP_DROP OP_14; -OP_15 OP_NIP <=> OP_DROP OP_15; -OP_16 OP_NIP <=> OP_DROP OP_16; - -OP_2 OP_PICK OP_SWAP OP_2 OP_PICK OP_NIP <=> OP_DROP OP_2DUP; - -# .slice(0, x) optimisation & .slice(x, y.length) optimisation -OP_0 OP_SPLIT OP_NIP <=> ; -OP_SIZE OP_SPLIT OP_DROP <=> ; - -# These are new optimisations that we cannot prove since CashProof doesn't work any more -# ////////////////////////////////////////////////////////////////////////////////////// - -OP_LESSTHAN OP_NOT <=> OP_GREATERTHANOREQUAL; -OP_GREATERTHAN OP_NOT <=> OP_LESSTHANOREQUAL; -OP_LESSTHANOREQUAL OP_NOT <=> OP_GREATERTHAN; -OP_GREATERTHANOREQUAL OP_NOT <=> OP_LESSTHAN; - -OP_TOALTSTACK OP_FROMALTSTACK <=> ; -`; diff --git a/packages/utils/src/optimisations.ts b/packages/utils/src/optimisations.ts index 06d22f2a..22d39925 100644 --- a/packages/utils/src/optimisations.ts +++ b/packages/utils/src/optimisations.ts @@ -1,4 +1,6 @@ -const provableOptimisations = [ +// Note: the order in which these optimisations are applied can impact the output, so entries should +// not be reordered without carefully verifying the compiled bytecode of existing contracts. +export const optimisationReplacements = [ // Hardcoded arithmetic ['OP_1 OP_ADD', 'OP_1ADD'], ['OP_1 OP_SUB', 'OP_1SUB'], @@ -119,33 +121,27 @@ const provableOptimisations = [ // .slice(0, x) optimisation & .slice(x, y.length) optimisation ['OP_0 OP_SPLIT OP_NIP', ''], ['OP_SIZE OP_SPLIT OP_DROP', ''], -] as [string, string][]; -const unprovableOptimisations = [ // Hardcoded arithmetic - // CashProof can't prove OP_IF without parameters ['OP_NOT OP_IF', 'OP_NOTIF'], + // Merge OP_VERIFY - // CashProof can't prove OP_CHECKMULTISIG without specifying N ['OP_CHECKMULTISIG OP_VERIFY', 'OP_CHECKMULTISIGVERIFY'], + // Remove/replace extraneous OP_SWAP - // CashProof can't prove bitwise operators ['OP_SWAP OP_AND', 'OP_AND'], ['OP_SWAP OP_OR', 'OP_OR'], ['OP_SWAP OP_XOR', 'OP_XOR'], // Remove/replace extraneous OP_DUP - // CashProof can't prove bitwise operators ['OP_DUP OP_AND', ''], ['OP_DUP OP_OR', ''], - // These are new optimisations that we cannot prove since CashProof doesn't work any more - // ////////////////////////////////////////////////////////////////////////////////////// - // TODO: Enable this optimisation when we overhaul the type system // (right now bool(4) == true => false, but !!bool(4) == true => true) so can't replace OP_NOT OP_NOT with '' // ['OP_NOT OP_NOT', ''] + // Invert comparison operators instead of negating them ['OP_LESSTHAN OP_NOT', 'OP_GREATERTHANOREQUAL'], ['OP_GREATERTHAN OP_NOT', 'OP_LESSTHANOREQUAL'], ['OP_LESSTHANOREQUAL OP_NOT', 'OP_GREATERTHAN'], @@ -154,7 +150,3 @@ const unprovableOptimisations = [ // This can get emitted by tuple destructuring ['OP_TOALTSTACK OP_FROMALTSTACK', ''], ] as [string, string][]; - -// Note: we moved these optimisations into a single file, but kept the exact same order as before, -// because the order in which optimisations are applied can impact the output. -export const optimisationReplacements = [...provableOptimisations, ...unprovableOptimisations]; diff --git a/packages/utils/src/script.ts b/packages/utils/src/script.ts index 4d114a5e..e82a8b18 100644 --- a/packages/utils/src/script.ts +++ b/packages/utils/src/script.ts @@ -8,7 +8,6 @@ import { OpcodesBch, AuthenticationInstruction, } from '@bitauth/libauth'; -import OptimisationsEquivFile from './cashproof-optimisations.js'; import { optimisationReplacements } from './optimisations.js'; import { range } from './data.js'; import { FullLocationData, PositionHint, SingleLocationData, SourceTagEntry, SourceTagKind } from './types.js'; @@ -222,57 +221,6 @@ function reconcileScopeCleanupTags(script: Script, sourceTags: SourceTagEntry[]) }); } -export function optimiseBytecodeOld(script: Script, runs: number = 1000): Script { - const optimisations = OptimisationsEquivFile - // Split by line and filter all line comments (#) - .split('\n') - .map((equiv) => equiv.trim()) - .filter((equiv) => !equiv.startsWith('#')) - // Join back the lines, and split on semicolon - .join('') - .split(';') - // Parse all optimisations in .equiv file - .map((equiv) => equiv.trim()) - .map((equiv) => equiv.split('<=>').map((part) => part.trim())) - .filter((equiv) => equiv.length === 2); - - for (let i = 0; i < runs; i += 1) { - const oldScript = script; - script = replaceOpsOld(script, optimisations); - - // Break on fixed point - if (scriptToAsm(oldScript) === scriptToAsm(script)) break; - } - - return script; -} - -function replaceOpsOld(script: Script, optimisations: string[][]): Script { - let asm = scriptToAsm(script); - - // Apply all optimisations in the cashproof file - optimisations.forEach(([pattern, replacement]) => { - asm = asm.replace(new RegExp(pattern, 'g'), replacement); - }); - - // Add optimisations that are not compatible with CashProof - // CashProof can't prove OP_IF without parameters - asm = asm.replace(/OP_NOT OP_IF/g, 'OP_NOTIF'); - // CashProof can't prove OP_CHECKMULTISIG without specifying N - asm = asm.replace(/OP_CHECKMULTISIG OP_VERIFY/g, 'OP_CHECKMULTISIGVERIFY'); - // CashProof can't prove bitwise operators - asm = asm.replace(/OP_SWAP OP_AND/g, 'OP_AND'); - asm = asm.replace(/OP_SWAP OP_OR/g, 'OP_OR'); - asm = asm.replace(/OP_SWAP OP_XOR/g, 'OP_XOR'); - asm = asm.replace(/OP_DUP OP_AND/g, ''); - asm = asm.replace(/OP_DUP OP_OR/g, ''); - - // Remove any double spaces as a result of opcode removal - asm = asm.replace(/\s+/g, ' ').trim(); - - return asmToScript(asm); -} - interface ReplaceOpsResult { script: Script; locationData: FullLocationData; From 0290f7d198bf12ee27bd4ce568ebc8bb8aa3af12 Mon Sep 17 00:00:00 2001 From: Rosco Kalis Date: Tue, 18 Aug 2026 11:07:35 +0200 Subject: [PATCH 2/3] Add safe OP_NOT OP_NOT optimisations --- packages/utils/src/optimisations.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/utils/src/optimisations.ts b/packages/utils/src/optimisations.ts index 22d39925..fb52c677 100644 --- a/packages/utils/src/optimisations.ts +++ b/packages/utils/src/optimisations.ts @@ -137,10 +137,6 @@ export const optimisationReplacements = [ ['OP_DUP OP_AND', ''], ['OP_DUP OP_OR', ''], - // TODO: Enable this optimisation when we overhaul the type system - // (right now bool(4) == true => false, but !!bool(4) == true => true) so can't replace OP_NOT OP_NOT with '' - // ['OP_NOT OP_NOT', ''] - // Invert comparison operators instead of negating them ['OP_LESSTHAN OP_NOT', 'OP_GREATERTHANOREQUAL'], ['OP_GREATERTHAN OP_NOT', 'OP_LESSTHANOREQUAL'], @@ -149,4 +145,12 @@ export const optimisationReplacements = [ // This can get emitted by tuple destructuring ['OP_TOALTSTACK OP_FROMALTSTACK', ''], + + // unsafe_bool(4) == true => false, but !!unsafe_bool(4) == true => true) so we can't replace OP_NOT OP_NOT with '' + // in the general case, but when it is followed by a consuming instruction that does not differentiate between + // true and truthy values (e.g. OP_IF, OP_UNTIL, OP_VERIFY), we can replace OP_NOT OP_NOT with '' + // Note that technically OP_NOT OP_NOT would also do a VM-number check, which gets removed by this optimisation + ['OP_NOT OP_NOT OP_UNTIL', 'OP_UNTIL'], + ['OP_NOT OP_NOTIF', 'OP_IF'], + ['OP_NOT OP_NOT OP_VERIFY', 'OP_VERIFY'], ] as [string, string][]; From 939791decce279d411beecf1f3eea50efcab8985 Mon Sep 17 00:00:00 2001 From: Rosco Kalis Date: Tue, 18 Aug 2026 11:18:57 +0200 Subject: [PATCH 3/3] Add fixture for double negation optimisation --- packages/cashc/test/generation/fixtures.ts | 49 +++++++++++++++++++ .../valid-contract-files/double_negation.cash | 22 +++++++++ 2 files changed, 71 insertions(+) create mode 100644 packages/cashc/test/valid-contract-files/double_negation.cash diff --git a/packages/cashc/test/generation/fixtures.ts b/packages/cashc/test/generation/fixtures.ts index 7157acdc..219eeb70 100644 --- a/packages/cashc/test/generation/fixtures.ts +++ b/packages/cashc/test/generation/fixtures.ts @@ -2010,4 +2010,53 @@ export const fixtures: Fixture[] = [ fingerprint: '3ab69a954ec4bf9ceeb58eceb8ead206195032edf6627155eb82f528403c94dd', }, }, + { + fn: 'double_negation.cash', + artifact: { + contractName: 'DoubleNegation', + constructorInputs: [{ name: 'flag', type: 'bool' }], + abi: [{ name: 'spend', inputs: [{ name: 'target', type: 'int' }] }], + bytecode: + // require(!!flag) - OP_NOT OP_NOT OP_VERIFY is optimised to OP_VERIFY + 'OP_DUP OP_VERIFY ' + // int i = 0; bool done = false; + + 'OP_0 OP_0 ' + // do { i = i + 1; + + 'OP_BEGIN OP_OVER OP_1ADD OP_ROT OP_DROP OP_SWAP ' + // done = i >= target; + + 'OP_OVER OP_4 OP_PICK OP_GREATERTHANOREQUAL OP_NIP ' + // } while (!done) - OP_NOT OP_NOT OP_UNTIL is optimised to OP_UNTIL + + 'OP_DUP OP_UNTIL ' + // if (!!flag) - OP_NOT OP_NOT OP_IF becomes OP_NOT OP_NOTIF, which is optimised to OP_IF + + 'OP_ROT OP_IF ' + // require(i == target); } + + 'OP_OVER OP_3 OP_PICK OP_NUMEQUALVERIFY OP_ENDIF ' + // require(i > 0) + + 'OP_SWAP OP_0 OP_GREATERTHAN ' + // clean up i and done + + 'OP_NIP OP_NIP', + debug: { + bytecode: '7669000065788b7b757c785479a27776667b637853799d687c00a07777', + sourceMap: '4:18:4:22;:8::24:1;6:16:6:17:0;7:20:7:25;9:8:13:24;10:16:10:17;:::21:1;:12::22;;;11:19:11:20:0;:24::30;;:19:::1;:12::31;13:18:13:22:0;9:8::24:1;16:14:16:18:0;:12:18:9;17:20:17:21;:25::31;;:12::33:1;16:20:18:9;20:16:20:17:0;:20::21;:8::23:1;2:31:21:5;', + logs: [], + requires: [ + { ip: 2, line: 4 }, + { ip: 23, line: 17 }, + { ip: 28, line: 20 }, + ], + sourceTags: '27:28:sc', + }, + source: fs.readFileSync(new URL('../valid-contract-files/double_negation.cash', import.meta.url), { encoding: 'utf-8' }), + compiler: { + name: 'cashc', + version, + options: { + enforceFunctionParameterTypes: true, + enforceLocktimeGuard: true, + }, + }, + updatedAt: '', + fingerprint: 'ed0b5bc35f0130fa04d1fce813fbd7c183bb5346006d10c0808f0d9872712ded', + }, + }, ]; diff --git a/packages/cashc/test/valid-contract-files/double_negation.cash b/packages/cashc/test/valid-contract-files/double_negation.cash new file mode 100644 index 00000000..55bcfb78 --- /dev/null +++ b/packages/cashc/test/valid-contract-files/double_negation.cash @@ -0,0 +1,22 @@ +contract DoubleNegation(bool flag) { + function spend(int target) { + // OP_NOT OP_NOT OP_VERIFY => OP_VERIFY + require(!!flag); + + int i = 0; + bool done = false; + + do { + i = i + 1; + done = i >= target; + // OP_NOT OP_NOT OP_UNTIL => OP_UNTIL + } while (!done); + + // OP_NOT OP_NOT OP_IF => OP_NOT OP_NOTIF => OP_IF + if (!!flag) { + require(i == target); + } + + require(i > 0); + } +}