Skip to content

Commit

Permalink
Merge pull request #185 from CashScript/next
Browse files Browse the repository at this point in the history
  • Loading branch information
rkalis authored Sep 10, 2024
2 parents 01f9b9b + 59162f0 commit d812d3b
Show file tree
Hide file tree
Showing 155 changed files with 12,095 additions and 2,746 deletions.
7 changes: 4 additions & 3 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ const path = require('path');
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
plugins: ['@typescript-eslint', 'import'],
extends: ['airbnb-typescript/base'],
parserOptions: {
project: path.join(__dirname, 'tsconfig.json'),
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
ecmaVersion: 2021, // Allows for the parsing of modern ECMAScript features
sourceType: 'module', // Allows for the use of imports
extraFileExtensions: ['.cjs'],
},
Expand Down Expand Up @@ -46,6 +46,7 @@ module.exports = {
'max-classes-per-file': 0, // Multiple classes in one file are allowed (e.g. Errors)
'@typescript-eslint/no-redeclare': 0, // I sometimes name variables an types the same
'linebreak-style': 0, // Ignore linebreak lints https://stackoverflow.com/a/43008668/1129108
'import/extensions': ['error', 'always'], // ESM requires file extensions
'import/extensions': ['error', 'ignorePackages'], // ESM requires file extensions
'@typescript-eslint/only-throw-error': 'error', // We should only throw Error objects
},
}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,5 @@ typings/

# DynamoDB Local files
.dynamodb/

manual-test.ts
50 changes: 49 additions & 1 deletion DEVELOPMENT.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
We use yarn workspaces + lerna for monorepo management. So to get started, clone this repository and run `yarn` in the root directory to install all dependencies for all packages.

When updating code in one package, you can run `yarn build` in the root directory to build all packages so the changes get propagated to the other packages as well. If you're already in a package directory, you can run the following command to do so:

```bash
pushd ../.. && yarn build && popd
```

### Publishing a release

To publish a new release, we use `yarn update-version 'x.x.x'` in the root directory to bump the version before release, and then `yarn publish-all` in the root directory to publish the release. In case of a tagged release (such as `next`), we use `TESTS_USE_MOCKNET=true yarn publish-all --dist-tag <tag name>` to publish the release with the specified tag.

## cashc

### Prerequisites
Expand All @@ -24,4 +36,40 @@ When updating the grammar file in `src/grammar/CashScript.g4`, we also need to m

```bash
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 <cashproof_path> [filenames]` to verify that the optimisations contained in these files are provably correct.

Example:
```bash
python <cashproof_path> 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 <cashproof_path> opt.equiv
```

## cashscript

### Running tests

By default, running tests in the `cashscript` package uses chipnet contracts, which requires the test accounts to have some chipnet BCH. To run the tests against a local "mock network", you can use the `TESTS_USE_MOCKNET` environment variable.

```bash
# Run all tests using the mock network
TESTS_USE_MOCKNET=true yarn test
```

To run specific tests, you can use the `-t` flag to match the name mentioned in the `it` or `describe` block:

```bash
# Run all tests in the 'Transaction Builder' describe block (test/e2e/transaction-builder/TransactionBuilder.test.ts)
yarn test -t 'Transaction Builder'
```
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
# CashScript

[![Build Status](https://travis-ci.org/Bitcoin-com/cashscript.svg)](https://travis-ci.org/Bitcoin-com/cashscript)
[![Coverage Status](https://img.shields.io/codecov/c/github/Bitcoin-com/cashscript.svg)](https://codecov.io/gh/Bitcoin-com/cashscript/)
[![Build Status](https://travis-ci.org/CashScript/cashscript.svg)](https://travis-ci.org/CashScript/cashscript)
[![Coverage Status](https://img.shields.io/codecov/c/github/CashScript/cashscript.svg)](https://codecov.io/gh/CashScript/cashscript/)
[![NPM Version](https://img.shields.io/npm/v/cashscript.svg)](https://www.npmjs.com/package/cashscript)
[![NPM Monthly Downloads](https://img.shields.io/npm/dm/cashscript.svg)](https://www.npmjs.com/package/cashscript)
[![NPM License](https://img.shields.io/npm/l/cashscript.svg)](https://www.npmjs.com/package/cashscript)

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/).

This repository contains the code for the CashScript compiler & command line tool under [`packages/cashc/`](/packages/cashc). This repository also contains the code for the CashScript JavaScript SDK under [`packages/cashscript/`](/packages/cashscript). The source code of the [CashScript.org](https://cashscript.org) website is included under [`website/`](/website). Visit the website for a detailed [Documentation](https://cashscript.org/docs/) on the CashScript language and SDK.
This repository contains the code for the CashScript compiler & command line tool under [`packages/cashc/`](/packages/cashc). This repository also contains the code for the CashScript TypeScript SDK under [`packages/cashscript/`](/packages/cashscript). The source code of the [CashScript.org](https://cashscript.org) website is included under [`website/`](/website). Visit the website for a detailed [Documentation](https://cashscript.org/docs/) on the CashScript language and SDK.

## The CashScript Language

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.

## The CashScript Compiler

CashScript features a compiler as a standalone command line tool, called `cashc`. It can be installed through npm and used to compile `.cash` files into `.json` artifact files. These artifact files can be imported into the CashScript JavaScript SDK (or other SDKs in the future). The `cashc` NPM package can also be imported inside JavaScript files to compile `.cash` files without using the command line tool.
CashScript features a compiler as a standalone command line tool, called `cashc`. It can be installed through npm and used to compile `.cash` files into `.json` artifact files. These artifact files can be imported into the CashScript TypeScript SDK (or other SDKs in the future). The `cashc` NPM package can also be imported inside JavaScript files to compile `.cash` files without using the command line tool.

### Installation

Expand Down Expand Up @@ -94,16 +94,16 @@ The "Hello World" of CashScript contracts is defining the P2PKH pattern inside a
To run the examples, clone this repository and navigate to the `examples/` directory. Since the examples depend on the SDK, be sure to run `npm install` or `yarn` inside the `examples/` directory, which installs all required packages.

```bash
git clone [email protected]:Bitcoin-com/cashscript.git
git clone [email protected]:CashScript/cashscript.git
cd cashscript/examples
npm install
```

All `.ts` files in the [`examples/`](/examples) directory can then be executed with `ts-node-esm`.
All `.ts` files in the [`examples/`](/examples) directory can then be executed with `tsx`.

```bash
npm install -g ts-node
ts-node-esm p2pkh.ts
npm install -g tsx
tsx p2pkh.ts
```

All `.js` files can be executed with `node`.
Expand Down
6 changes: 3 additions & 3 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ cd cashscript/examples
yarn
```

All `.ts` files can then be executed with `ts-node-esm`.
All `.ts` files can then be executed with `tsx`.

```bash
npm install -g ts-node
ts-node-esm p2pkh.ts
npm install -g tsx
tsx p2pkh.ts
```

All `.js` files can be executed with `node`.
Expand Down
2 changes: 1 addition & 1 deletion examples/announcement.cash
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma cashscript ^0.9.0;
pragma cashscript ^0.10.0;

/* This is a contract showcasing covenants outside of regular transactional use.
* It enforces the contract to make an "announcement" on Memo.cash, and send the
Expand Down
4 changes: 2 additions & 2 deletions examples/common-js.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import bip39 from 'bip39';

// Generate entropy from BIP39 mnemonic phrase and initialise a root HD-wallet node
const seed = await bip39.mnemonicToSeed('CashScript Examples');
const rootNode = deriveHdPrivateNodeFromSeed(seed, true);
const rootNode = deriveHdPrivateNodeFromSeed(seed, { assumeValidity: true, throwErrors: true });
const baseDerivationPath = "m/44'/145'/0'/0";

// Derive Alice's private key, public key, public key hash and address
Expand All @@ -20,4 +20,4 @@ if (typeof aliceNode === 'string') throw new Error();
export const alicePub = secp256k1.derivePublicKeyCompressed(aliceNode.privateKey);
export const alicePriv = aliceNode.privateKey;
export const alicePkh = hash160(alicePub);
export const aliceAddress = encodeCashAddress('bchtest', 'p2pkh', alicePkh);
export const aliceAddress = encodeCashAddress({ prefix: 'bchtest', type: 'p2pkh', payload: alicePkh, throwErrors: true }).address;
8 changes: 4 additions & 4 deletions examples/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { PriceOracle } from './PriceOracle.js';

// Generate entropy from BIP39 mnemonic phrase and initialise a root HD-wallet node
const seed = await bip39.mnemonicToSeed('CashScript Examples');
const rootNode = deriveHdPrivateNodeFromSeed(seed, true);
const rootNode = deriveHdPrivateNodeFromSeed(seed, { assumeValidity: true, throwErrors: true });
const baseDerivationPath = "m/44'/145'/0'/0";

// Derive Alice's private key, public key, public key hash and address
Expand All @@ -19,15 +19,15 @@ if (typeof aliceNode === 'string') throw new Error();
export const alicePub = secp256k1.derivePublicKeyCompressed(aliceNode.privateKey) as Uint8Array;
export const alicePriv = aliceNode.privateKey;
export const alicePkh = hash160(alicePub);
export const aliceAddress = encodeCashAddress('bchtest', 'p2pkhWithTokens', alicePkh);
export const aliceAddress = encodeCashAddress({ prefix: 'bchtest', type: 'p2pkhWithTokens', payload: alicePkh, throwErrors: true }).address;

// Derive Bob's private key, public key, public key hash and address
const bobNode = deriveHdPath(rootNode, `${baseDerivationPath}/1`);
if (typeof bobNode === 'string') throw new Error();
export const bobPub = secp256k1.derivePublicKeyCompressed(bobNode.privateKey) as Uint8Array;
export const bobPriv = bobNode.privateKey;
export const bobPkh = hash160(bobPub);
export const bobAddress = encodeCashAddress('bchtest', 'p2pkhWithTokens', bobPkh);
export const bobAddress = encodeCashAddress({ prefix: 'bchtest', type: 'p2pkhWithTokens', payload: bobPkh, throwErrors: true }).address;

// Initialise a price oracle with a private key
const oracleNode = deriveHdPath(rootNode, `${baseDerivationPath}/2`);
Expand All @@ -36,4 +36,4 @@ export const oraclePub = secp256k1.derivePublicKeyCompressed(oracleNode.privateK
export const oraclePriv = oracleNode.privateKey;
export const oracle = new PriceOracle(oracleNode.privateKey);
export const oraclePkh = hash160(oraclePub);
export const oracleAddress = encodeCashAddress('bchtest', 'p2pkhWithTokens', oraclePkh);
export const oracleAddress = encodeCashAddress({ prefix: 'bchtest', type: 'p2pkhWithTokens', payload: oraclePkh, throwErrors: true }).address;
2 changes: 1 addition & 1 deletion examples/hodl_vault.cash
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma cashscript ^0.9.0;
pragma cashscript ^0.10.0;

// This contract forces HODLing until a certain price target has been reached
// A minimum block is provided to ensure that oracle price entries from before this block are disregarded
Expand Down
2 changes: 1 addition & 1 deletion examples/mecenas.cash
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma cashscript ^0.9.0;
pragma cashscript ^0.10.0;

/* This is an unofficial CashScript port of Licho's Mecenas contract. It is
* not compatible with Licho's EC plugin, but rather meant as a demonstration
Expand Down
2 changes: 1 addition & 1 deletion examples/mecenas_locktime.cash
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma cashscript ^0.9.0;
pragma cashscript ^0.10.0;

// This is an experimental contract for a more "streaming" Mecenas experience
// Completely untested, just a concept
Expand Down
2 changes: 1 addition & 1 deletion examples/p2pkh.cash
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma cashscript ^0.9.0;
pragma cashscript ^0.10.0;

contract P2PKH(bytes20 pkh) {
// Require pk to match stored pkh and signature to match
Expand Down
12 changes: 8 additions & 4 deletions examples/package.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
{
"name": "cashscript-examples",
"private": true,
"version": "0.9.3",
"version": "0.10.0",
"description": "Usage examples of the CashScript SDK",
"main": "p2pkh.js",
"type": "module",
"author": "Rosco Kalis <[email protected]>",
"license": "MIT",
"scripts": {
"lint": "eslint . --ext .ts --ignore-path ../.eslintignore"
},
"dependencies": {
"@bitauth/libauth": "^2.0.0-alpha.8",
"@bitauth/libauth": "^3.0.0",
"@types/node": "^12.7.8",
"bip39": "^3.0.4",
"cashc": "^0.9.3",
"cashscript": "^0.9.3",
"cashc": "^0.10.0",
"cashscript": "^0.10.0",
"eslint": "^8.56.0",
"typescript": "^4.9.5"
}
}
47 changes: 47 additions & 0 deletions examples/testing-suite/artifacts/example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"contractName": "Example",
"constructorInputs": [],
"abi": [
{
"name": "test",
"inputs": [
{
"name": "value",
"type": "int"
}
]
}
],
"bytecode": "OP_1 OP_NUMEQUAL",
"source": "contract Example() {\n function test(int value) {\n console.log(value, \"test\");\n require(value == 1, \"Wrong value passed\");\n }\n}\n",
"debug": {
"bytecode": "007a519c",
"sourceMap": "4:12:4:17;;:21::22;:12:::1",
"logs": [
{
"ip": 0,
"line": 3,
"data": [
{
"stackIndex": 0,
"type": "int",
"ip": 0
},
"test"
]
}
],
"requires": [
{
"ip": 4,
"line": 4,
"message": "Wrong value passed"
}
]
},
"compiler": {
"name": "cashc",
"version": "0.10.0"
},
"updatedAt": "2024-09-10T09:55:42.448Z"
}
6 changes: 6 additions & 0 deletions examples/testing-suite/contracts/example.cash
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
contract Example() {
function test(int value) {
console.log(value, "test");
require(value == 1, "Wrong value passed");
}
}
6 changes: 6 additions & 0 deletions examples/testing-suite/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default {
transform: {},
roots: ['./dist-test'],
testEnvironment: 'jest-environment-node',
setupFilesAfterEnv: ['./jest.setup.js'],
};
8 changes: 8 additions & 0 deletions examples/testing-suite/jest.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { jest } from '@jest/globals';
import { inspect } from 'util';
inspect.defaultOptions.depth = 10;

jest.setTimeout(50000);

globalThis.jest = jest;

39 changes: 39 additions & 0 deletions examples/testing-suite/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"name": "testing-suite",
"version": "0.10.0",
"description": "Example project to develop and test CashScript contracts",
"main": "index.js",
"type": "module",
"author": "mainnet-pat",
"license": "MIT",
"private": true,
"directories": {
"lib": "src",
"test": "test"
},
"scripts": {
"build": "yarn clean && yarn compile",
"build:test": "yarn clean:test && yarn compile:test",
"clean": "rm -rf ./dist",
"clean:test": "rm -rf ./dist-test",
"compile": "tsc -p tsconfig.json && tsx tasks/index.ts compile",
"compile:test": "tsc -p tsconfig.test.json && tsx tasks/index.ts compile",
"lint": "eslint . --ext .ts --ignore-path ../../.eslintignore",
"prepare": "yarn build",
"prepublishOnly": "yarn test && yarn lint",
"pretest": "yarn build:test",
"test": "NODE_OPTIONS='--experimental-vm-modules --no-warnings' jest"
},
"dependencies": {
"cashc": "^0.10.0",
"cashscript": "^0.10.0",
"url-join": "^5.0.0"
},
"devDependencies": {
"@jest/globals": "^29.4.1",
"@types/jest": "^29.4.1",
"jest": "^29.4.1",
"tsx": "^4.7.2",
"typescript": "^4.9.5"
}
}
Empty file.
26 changes: 26 additions & 0 deletions examples/testing-suite/tasks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { compileString } from 'cashc';
import fs from 'fs';
import { URL } from 'url';
import urlJoin from 'url-join';

export const compile = (): void => {
const directory = new URL('../contracts', import.meta.url);
const result = fs.readdirSync(directory)
.filter((fn) => fn.endsWith('.cash'))
.map((fn) => ({ fn, contents: fs.readFileSync(new URL(urlJoin(directory.toString(), fn)), { encoding: 'utf-8' }) }));

result.forEach(({ fn, contents }) => {
const artifact = compileString(contents);

fs.writeFileSync(new URL(`../artifacts/${fn.replace('.cash', '.json')}`, import.meta.url), JSON.stringify(artifact, null, 2));
});
};

switch (process.argv[2]) {
case 'compile':
compile();
break;
default:
console.log('Unknown task');
break;
}
Loading

0 comments on commit d812d3b

Please sign in to comment.