Skip to content

Commit

Permalink
Merge pull request #122 from Bitcoin-com/next
Browse files Browse the repository at this point in the history
  • Loading branch information
rkalis authored May 24, 2022
2 parents 23c8985 + 33dcb01 commit ebbbcc4
Show file tree
Hide file tree
Showing 190 changed files with 3,631 additions and 2,900 deletions.
16 changes: 16 additions & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"language": "en-GB",
"words": [
"aave",
"ACTIVEBYTECODE",
"addrs",
"algolia",
"altstack",
Expand Down Expand Up @@ -73,6 +74,9 @@
"hodling",
"htlc",
"incentivise",
"INPUTBYTECODE",
"INPUTINDEX",
"INPUTSEQUENCENUMBER",
"kalis",
"keypair",
"keypairs",
Expand All @@ -97,6 +101,7 @@
"nonschnorr",
"nops",
"notif",
"nullary",
"nulldata",
"nullfail",
"numequal",
Expand All @@ -106,7 +111,11 @@
"opcode",
"opcodes",
"opcount",
"OUTPOINTINDEX",
"OUTPOINTTXHASH",
"output",
"OUTPUTBYTECODE",
"OUTPUTVALUE",
"patreon",
"pein",
"pkh",
Expand Down Expand Up @@ -151,11 +160,17 @@
"tuple",
"tuples",
"txid",
"TXINPUTCOUNT",
"TXLOCKTIME",
"TXOUTPUTCOUNT",
"txvalue",
"TXVERSION",
"unary",
"utxo",
"utxo's",
"UTXOBYTECODE",
"utxos",
"UTXOVALUE",
"vars",
"vimrc",
"vout",
Expand All @@ -168,6 +183,7 @@
"docu",
"infima",
"lichos",
"maxdepth",
"networkprovider",
"outputnulldata",
"outputp",
Expand Down
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,6 @@ 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 extensins
},
}
16 changes: 7 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,13 @@ npm install -g cashc
Usage: cashc [options] [source_file]

Options:
--output, -o Specify a file to output the generated artifact. [string]
--hex, -h Compile the contract to hex format rather than a full artifact
[boolean]
--asm, -A Compile the contract to ASM format rather than a full artifact
[boolean]
--opcount, -c Display the number of opcodes in the compiled bytecode[boolean]
--size, -s Display the size in bytes of the compiled bytecode [boolean]
--help Show help [boolean]
--version Show version number [boolean]
-V, --version Output the version number.
-o, --output <path> Specify a file to output the generated artifact.
-h, --hex Compile the contract to hex format rather than a full artifact.
-A, --asm Compile the contract to ASM format rather than a full artifact.
-c, --opcount Display the number of opcodes in the compiled bytecode.
-s, --size Display the size in bytes of the compiled bytecode.
-?, --help Display help
```

## The CashScript SDK
Expand Down
29 changes: 14 additions & 15 deletions examples/announcement.cash
Original file line number Diff line number Diff line change
@@ -1,29 +1,28 @@
pragma cashscript ^0.6.0;
pragma cashscript ^0.7.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
* remainder of contract funds back to the contract.
*/
contract Announcement() {
function announce(pubkey pk, sig s) {
require(checkSig(s, pk));

function announce() {
// Create the memo.cash announcement output
bytes announcement = new OutputNullData([
bytes announcement = new LockingBytecodeNullData([
0x6d02,
bytes('A contract may not injure a human being or, through inaction, allow a human being to come to harm.')
]);

// Calculate leftover money after fee (2000 sats)
// Add change output if the remainder can be used
// otherwise donate the remainder to the miner
int minerFee = 2000;
int changeAmount = int(bytes(tx.value)) - minerFee;
if (changeAmount >= (minerFee / 2)) {
bytes32 change = new OutputP2SH(bytes8(changeAmount), hash160(tx.bytecode));
require(tx.hashOutputs == hash256(announcement + change));
} else {
require(tx.hashOutputs == hash256(announcement));
// Check that the first tx output matches the announcement
require(tx.outputs[0].value == 0);
require(tx.outputs[0].lockingBytecode == announcement);

// Calculate leftover money after fee (1000 sats)
// Check that the second tx output sends the change back if there's enough leftover for another announcement
int minerFee = 1000;
int changeAmount = tx.inputs[this.activeInputIndex].value - minerFee;
if (changeAmount >= minerFee) {
require(tx.outputs[1].lockingBytecode == tx.inputs[this.activeInputIndex].lockingBytecode);
require(tx.outputs[1].value == changeAmount);
}
}
}
20 changes: 4 additions & 16 deletions examples/announcement.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,16 @@
import { BITBOX } from 'bitbox-sdk';
import { Contract, SignatureTemplate, ElectrumNetworkProvider } from 'cashscript';
import { Contract, ElectrumNetworkProvider } from 'cashscript';
import { compileFile } from 'cashc';
import path from 'path';
import { stringify } from '@bitauth/libauth';

run();
export async function run(): Promise<void> {
try {
// Initialise BITBOX ---- ATTENTION: Set to mainnet
const bitbox = new BITBOX();

// Initialise HD node and alice's keypair
const rootSeed = bitbox.Mnemonic.toSeed('CashScript');
const hdNode = bitbox.HDNode.fromSeed(rootSeed);
const alice = bitbox.HDNode.toKeyPair(bitbox.HDNode.derive(hdNode, 0));

// Derive alice's public key
const alicePk = bitbox.ECPair.toPublicKey(alice);

// Compile the Announcement contract to an artifact object
const artifact = compileFile(path.join(__dirname, 'announcement.cash'));

// Initialise a network provider for network operations on MAINNET
const provider = new ElectrumNetworkProvider('mainnet');
const provider = new ElectrumNetworkProvider();

// Instantiate a new contract using the compiled artifact and network provider
// AND providing the constructor parameters (none)
Expand All @@ -40,9 +28,9 @@ export async function run(): Promise<void> {
// for another announcement.
const str = 'A contract may not injure a human being or, through inaction, allow a human being to come to harm.';
const tx = await contract.functions
.announce(alicePk, new SignatureTemplate(alice))
.announce()
.withOpReturn(['0x6d02', str])
.withHardcodedFee(2000)
.withHardcodedFee(1000)
.withMinChange(1000)
.send();

Expand Down
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.6.0;
pragma cashscript ^0.7.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/hodl_vault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { BITBOX } from 'bitbox-sdk';
import { Contract, SignatureTemplate, ElectrumNetworkProvider } from 'cashscript';
import { compileFile } from 'cashc';
import path from 'path';
import { PriceOracle } from './PriceOracle';
import { PriceOracle } from './PriceOracle.js';

run();
async function run(): Promise<void> {
Expand Down
38 changes: 15 additions & 23 deletions examples/mecenas.cash
Original file line number Diff line number Diff line change
@@ -1,37 +1,29 @@
pragma cashscript ^0.6.0;
pragma cashscript ^0.7.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
* of covenants in CashScript.
* The time checking has been removed so it can be tested without time requirements.
*/
contract Mecenas(bytes20 recipient, bytes20 funder, int pledge/*, int period*/) {
function receive(pubkey pk, sig s) {
require(checkSig(s, pk));

contract Mecenas(bytes20 recipient, bytes20 funder, int pledge/*, int period */) {
function receive() {
// require(tx.age >= period);

int minerFee = 1000;
int intValue = int(bytes(tx.value));
// Check that the first output sends to the recipient
require(tx.outputs[0].lockingBytecode == new LockingBytecodeP2PKH(recipient));

if (intValue <= pledge + minerFee) {
/* The contract has less value than the pledge, or equal.
* The recipient must claim all of of it. */
int minerFee = 1000;
int currentValue = tx.inputs[this.activeInputIndex].value;
int changeValue = currentValue - pledge - minerFee;

bytes8 amount1 = bytes8(intValue - minerFee);
bytes34 out1 = new OutputP2PKH(amount1, recipient);
require(hash256(out1) == tx.hashOutputs);
// If there is not enough left for *another* pledge after this one, we send the remainder to the recipient
// Otherwise we send the remainder to the recipient and the change back to the contract
if (changeValue <= pledge + minerFee) {
require(tx.outputs[0].value == currentValue - minerFee);
} else {
/* The contract has more value than the pledge. The recipient must
* also add one change output sending the remaining coins back
* to the contract.
*/

bytes8 amount1 = bytes8(pledge);
bytes8 amount2 = bytes8(intValue - pledge - minerFee);
bytes34 out1 = new OutputP2PKH(amount1, recipient);
bytes32 out2 = new OutputP2SH(amount2, hash160(tx.bytecode));
require(hash256(out1 + out2) == tx.hashOutputs);
require(tx.outputs[0].value == pledge);
require(tx.outputs[1].lockingBytecode == tx.inputs[this.activeInputIndex].lockingBytecode);
require(tx.outputs[1].value == changeValue);
}
}

Expand Down
8 changes: 4 additions & 4 deletions examples/mecenas.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { stringify } from '@bitauth/libauth';
import { BITBOX } from 'bitbox-sdk';
import { Contract, SignatureTemplate, ElectrumNetworkProvider } from 'cashscript';
import { Contract, ElectrumNetworkProvider } from 'cashscript';
import { compileFile } from 'cashc';
import path from 'path';

Expand Down Expand Up @@ -29,8 +29,8 @@ export async function run(): Promise<void> {
// Compile the Mecenas contract to an artifact object
const artifact = compileFile(path.join(__dirname, 'mecenas.cash'));

// Initialise a network provider for network operations on TESTNET
const provider = new ElectrumNetworkProvider('testnet');
// Initialise a network provider for network operations on MAINNET
const provider = new ElectrumNetworkProvider();

// Instantiate a new contract using the compiled artifact and network provider
// AND providing the constructor parameters:
Expand All @@ -47,7 +47,7 @@ export async function run(): Promise<void> {
// Will send one pledge amount to alice, and send change back to the contract
// Manually set fee to 1000 because this is hardcoded in the contract
const tx = await contract.functions
.receive(alicePk, new SignatureTemplate(alice))
.receive()
.to(aliceAddress, 10000)
.withHardcodedFee(1000)
.send();
Expand Down
63 changes: 38 additions & 25 deletions examples/mecenas_locktime.cash
Original file line number Diff line number Diff line change
@@ -1,38 +1,51 @@
pragma cashscript ^0.6.0;
pragma cashscript ^0.7.0;

// This is an experimental contract for a more "streaming" Mecenas experience
// Completely untested, just a concept
contract Mecenas(bytes4 initialBlock, int pledgePerBlock, bytes20 recipient, bytes20 funder) {
function receive(pubkey pk, sig s, int pledge) {
require(checkSig(s, pk));
contract Mecenas(
bytes20 recipient,
bytes20 funder,
int pledgePerBlock,
bytes8 initialBlock,
) {
function receive() {
// Check that the first output sends to the recipient
bytes25 recipientLockingBytecode = new LockingBytecodeP2PKH(recipient);
require(tx.outputs[0].lockingBytecode == recipientLockingBytecode);

// Check that time has passed and that time locks are enabled
int initial = int(initialBlock);
require(tx.time >= initial);

// Pledge amount calculation is done in client, verified in contract
// When OP_MUL is enabled this can be done in contract
// Double require to account for integer division
int passedBlocks = int(tx.locktime) - initial;
require(pledge / passedBlocks == pledgePerBlock);
require((pledge - 1) / passedBlocks != pledgePerBlock);

// Cut out old initialBlock (PUSH1 0x04 <inheritor>)
// Insert new initialBlock (PUSH1 0x04 <newInheritor>)
bytes newContract = 0x4c04 + tx.locktime + tx.bytecode.split(6)[1];
// Calculate the amount that has accrued since last claim
int passedBlocks = tx.locktime - initial;
int pledge = passedBlocks * pledgePerBlock;

int minerFee = 1000; // hardcoded fee
int intValue = int(bytes(tx.value));
// Calculate the leftover amount
int minerFee = 1000;
int currentValue = tx.inputs[this.activeInputIndex].value;
int changeValue = currentValue - pledge - minerFee;

if (intValue <= pledge + minerFee) {
bytes8 amount1 = bytes8(intValue - minerFee);
bytes34 out1 = new OutputP2PKH(amount1, recipient);
require(hash256(out1) == tx.hashOutputs);
// If there is not enough left for *another* block after this one,
// we send the remainder to the recipient. Otherwise we send the
// remainder to the recipient and the change back to the contract
if (changeValue <= pledgePerBlock + minerFee) {
require(tx.outputs[0].value == currentValue - minerFee);
} else {
bytes8 amount1 = bytes8(pledge);
bytes8 amount2 = bytes8(intValue - pledge - minerFee);
bytes34 out1 = new OutputP2PKH(amount1, recipient);
bytes32 out2 = new OutputP2SH(amount2, hash160(newContract));
require(hash256(out1 + out2) == tx.hashOutputs);
// Check that the outputs send the correct amounts
require(tx.outputs[0].value == pledge);
require(tx.outputs[1].value == changeValue);

// Cut out old initialBlock (OP_PUSHBYTES_8 <initialBlock>)
// Insert new initialBlock (OP_PUSHBYTES_8 <tx.locktime>)
// Note that constructor parameters are added in reverse order,
// so initialBlock is the first statement in the contract bytecode.
bytes newContract = 0x08 + bytes8(tx.locktime) + this.activeBytecode.split(9)[1];

// Create the locking bytecode for the new contract and check that
// the change output sends to that contract
bytes23 newContractLock = new LockingBytecodeP2SH(hash160(newContract));
require(tx.outputs[1].lockingBytecode == newContractLock);
}
}

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.6.0;
pragma cashscript ^0.7.0;

contract P2PKH(bytes20 pkh) {
// Require pk to match stored pkh and signature to match
Expand Down
6 changes: 3 additions & 3 deletions examples/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cashscript-examples",
"version": "1.0.0",
"version": "0.7.0",
"description": "Usage examples of the CashScript SDK",
"main": "p2pkh.js",
"author": "Rosco Kalis <rosco@bitcoin.com>",
Expand All @@ -10,8 +10,8 @@
"@types/node": "^12.7.8",
"bitbox-sdk": "^8.11.1",
"bitcoincashjs-lib": "Bitcoin-com/bitcoincashjs-lib#v4.0.1",
"cashc": "^0.6.0",
"cashscript": "^0.6.0",
"cashc": "^0.7.0",
"cashscript": "^0.7.0",
"typescript": "^3.6.4"
}
}
2 changes: 1 addition & 1 deletion examples/transfer_with_timeout.cash
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma cashscript ^0.6.0;
pragma cashscript ^0.7.0;

contract TransferWithTimeout(
pubkey sender,
Expand Down
4 changes: 2 additions & 2 deletions examples/webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
"@types/react-dom": "^16.9.0",
"bitbox-sdk": "^8.11.2",
"bootstrap": "^4.5.2",
"cashc": "^0.6.0",
"cashscript": "^0.6.0",
"cashc": "^0.7.0-next.0",
"cashscript": "^0.7.0-next.0",
"react": "^16.13.1",
"react-bootstrap": "^1.3.0",
"react-dom": "^16.13.1",
Expand Down
Loading

0 comments on commit ebbbcc4

Please sign in to comment.