codenlighten/scrypt
0
1---2sidebar_position: 13---4 5# How to Write a Contract6 7A smart contract is a class that extends the `SmartContract` base class. A simple example is shown below.8 9```ts10import { SmartContract, method, prop, assert } from "scrypt-ts"11 12class Demo extends SmartContract {13 @prop()14 readonly x: bigint15 16 constructor(x: bigint) {17 super(...arguments)18 this.x = x19 }20 21 @method()22 public unlock(x: bigint) {23 assert(this.add(this.x, 1n) == x, 'incorrect sum')24 }25 26 @method()27 add(x0: bigint, x1:bigint) : bigint {28 return x0 + x129 }30}31```32 33Class members decorated with `@prop` and `@method` will end up on the blockchain and thus must be a strict subset of TypeScript. Everywhere decorated with them can be regarded in the on-chain context. Members decorated with neither are regular TypeScript and are kept off chain. The significant benefit of `sCrypt` is that both on-chain and off-chain code are written in the same language: TypeScript.34 35:::note36You can use [the sCrypt template Repl](https://replit.com/@msinkec/sCrypt) and play with the code in your browser!37:::38 39## Properties40 41A smart contract can have two kinds of properties:42 431. With `@prop` decorator: these properties are **only allowed to have [types](#data-types) specified below** and they shall only be initialized in the constructor.44 452. Without `@prop` decorator: these properties are regular TypeScript properties without any special requirement, meaning they can use any types. Accessing these properties is prohibited in methods decorated with the `@method` decorator.46 47 48### `@prop` decorator49 50Use this decorator to mark any property that intends to be stored on chain.51 52This decorator takes a `boolean` parameter. By default, it is set to `false`, meaning the property cannot be changed after the contract is deployed. If the value is `true`, the property is a so-called [stateful](./stateful-contract) property and its value can be updated in subsequent contract calls.53 54```ts55// good, `a` is stored on chain, and it's readonly after the contract is deployed56@prop()57readonly a: bigint58 59// valid, but not good enough, `a` cannot be changed after the contract is deployed60@prop()61a: bigint62 63// good, `b` is stored on chain, and its value can be updated in subsequent contract calls64@prop(true)65b: bigint66 67// invalid, `b` is a stateful property that cannot be readonly68@prop(true)69readonly b: bigint70 71// good72@prop()73static c: bigint = 1n74 75// invalid, static property must be initialized when declared76@prop()77static c: bigint78 79// invalid, stateful property cannot be static80@prop(true)81static c: bigint = 1n82 83// good, `UINT_MAX` is a compile-time constant, and no need to typed explicitly84static readonly UINT_MAX = 0xffffffffn85 86// valid, but not good enough, `@prop()` is not necessary for the CTC87@prop()88static readonly UINT_MAX = 0xffffffffn89 90// invalid91@prop(true)92static readonly UINT_MAX = 0xffffffffn93```94 95## Constructor96 97A smart contract must have an explicit constructor if it has at least one `@prop` that is not `static`.98 99The `super` method **must** be called in the constructor and all the arguments of the constructor should be passed to `super`100in the same order as they are passed into the constructor. For example,101 102```ts103class A extends SmartContract {104 readonly p0: bigint105 106 @prop()107 readonly p1: bigint108 109 @prop()110 readonly p2: boolean111 112 constructor(p0: bigint, p1: bigint, p2: boolean) {113 super(...arguments) // same as super(p0, p1, p2)114 this.p0 = p0115 this.p1 = p1116 this.p2 = p2117 }118}119```120[`arguments`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments) is an array containing the values of the arguments passed to that function. `...` is the [spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax).121 122 123## Methods124 125Like properties, a smart contract can also have two kinds of methods:126 1271. With `@method` decorator: these methods can only call **methods also decorated by `@method` or [functions](#functions) specified below**. Also, **only the properties decorated by `@prop` can be accessed**.128 1292. Without `@method` decorator: these methods are just regular TypeScript class methods.130 131 132### `@method` decorator133 1341. Use this decorator to mark any method that intends to run on chain.1352. It takes a [sighash flag](./scriptcontext.md#sighash-type) as a parameter.136 137 138### Public `@method`s139 140Each contract **must** have at least one public `@method`. It is denoted with the `public` modifier and does not return any value. It is visible outside the contract and acts as the main method into the contract (like `main` in C and Java).141 142A public `@method` can be called from an external transaction. The call succeeds if it runs to completion without violating any conditions in [assert()](./built-ins.md#assert). An example is shown below.143 144```ts145@method()146public unlock(x: bigint) {147 // only succeeds if x is 1148 assert(this.add(this.x, 1n) == x, "unequal")149}150```151 152:::note153The last function call of a public `@method` method **must** be an `assert()` function call, unless it is a `console.log()` call.154:::155 156```ts157class PublicMethodDemo extends SmartContract {158 @method()159 public foo() {160 // invalid, the last statement of public method should be an `assert` function call161 }162 163 @method()164 public bar() {165 assert(true);166 return 1n; // invalid, because a public method cannot return any value167 }168 169 @method()170 public foobar() {171 console.log();172 // valid, `console.log` calling will be ignored when verifying the last `assert` statement173 assert(true);174 console.log();175 console.log();176 }177}178```179 180### Non-public `@method`s181 182Without a `public` modifier, a `@method` is internal and cannot be directly called from an external transaction.183 184```ts185@method()186add(x0: bigint, x1:bigint) : bigint {187 return x0 + x1188}189```190 191:::note192**Recursion is disallowed**. A `@method`, public and not, cannot call itself, either directly in its own body or indirectly calls another method that transitively calls itself.193:::194 195```ts196class MethodsDemo extends SmartContract {197 @prop()198 readonly x: bigint;199 @prop()200 readonly y: bigint;201 202 constructor(x: bigint, y: bigint) {203 super(...arguments);204 this.x = x;205 this.y = y;206 }207 208 // good, non-public static method without access `@prop` properties209 @method()210 static sum(a: bigint, b: bigint): bigint {211 return a + b;212 }213 214 // good, non-public method215 @method()216 xyDiff(): bigint {217 return this.x - this.y218 }219 220 // good, public method221 @method()222 public add(z: bigint) {223 // good, call `sum` with the class name224 assert(z == MethodsDemo.sum(this.x, this.y), 'add check failed');225 }226 227 // good, another public method228 @method()229 public sub(z: bigint) {230 // good, call `xyDiff` with the class instance231 assert(z == this.xyDiff(), 'sub check failed');232 }233 234 // valid but bad, public static method235 @method()236 public static alwaysPass() {237 assert(true)238 }239}240```241 242## Data Types243 244Types used in `@prop` and `@method` are restricted to these kinds:245 246### Basic Types247 248#### boolean249 250A simple value `true` or `false`.251```ts252let isDone: boolean = false253```254 255#### `bigint`256 257`bigint` can represent arbitrarily large integers. A [bigint literal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) is a number with suffix `n`:258 259```ts26011n2610x33FEn262const previouslyMaxSafeInteger = 9007199254740991n263const alsoHuge = BigInt(9007199254740991)264// 9007199254740991n265const hugeHex: bigint = BigInt("0x1fffffffffffff")266// 9007199254740991n267```268 269#### `ByteString`270 271In a smart contract context (i.e., in `@method`s or `@prop`s), a `ByteString` represents a byte array.272 273A literal `string` can be converted in to a `ByteString` using function `toByteString(literal: string, isUtf8: boolean = false): ByteString`:274 275* If not passing `isUtf8` or `isUtf8` is `false`, then `literal` should be in the format of hex literal, which can be represented by the regular expression: `/^([0-9a-fA-F]{2})*$/`276* Otherwise, `literal` should be in the format of utf8 literal, e.g., `hello world`.277 278:::note279`toByteString` **ONLY** accepts string literals for its first argument, and boolean literals for the second.280:::281 282```ts283let a = toByteString('0011') // valid, `0011` is a valid hex literal284// 0011285let b = toByteString('hello world', true) // valid286// 68656c6c6f20776f726c64287 288toByteString('0011', false) // valid289// 30303131290 291toByteString(b, true) // invalid, not passing string literal to the 1st parameter292 293toByteString('001') // invalid, `001` is not a valid hex literal294toByteString('hello', false) // invalid, `hello` is not a valid hex literal295 296toByteString('hello', 1 === 1) // invalid, not passing boolean literal to the 2nd parameter297 298let c = true299toByteString('world', c) // invalid, not passing boolean literal to the 2nd parameter300```301 302`ByteString` has the following operators and methods:303 304* `==` / `===`: compare305 306* `+`: concatenate307 308```ts309const str0 = toByteString('01ab23ef68')310const str1 = toByteString('656c6c6f20776f726c64')311 312// comparison313str0 == str1314str0 === str1315// false316 317// concatenation318str0 + str1319// '01ab23ef68656c6c6f20776f726c64'320```321 322#### `number`323 324Type `number` is not allowed in `@prop`s and `@method`s, except in the following cases. We can use `Number()` function to convert `bigint` to `number`.325 326* Array index327 328```ts329let arr: FixedArray<bigint, 3> = [1n, 3n, 3n]330let idx: bigint = 2n331let item = arr[Number(idx)]332```333 334* Loop variable335 336``` ts337for (let i: number = 0 i < 10 i++) {338 let j: bigint = BigInt(i) // convert number to bigint339}340```341 342It can also be used in defining [compile-time constants](#compile-time-constant).343 344 345### Fixed Size Array346 347All arrays **must** be of fixed size and be declared as type of `FixedArray<T, SIZE>`, whose `SIZE` must be a [CTC](#compile-time-constant) described later.348The common TypeScript arrays declared as `T[]` or `Array<T>` are not allowed in `@prop`s and `@method`s, as they are of dynamic size.349 350```ts351let aaa: FixedArray<bigint, 3> = [1n, 3n, 3n]352 353// set to all 0s354const N = 20355let aab: FixedArray<bigint, N> = fill(0n, N)356 357// 2-dimensional array358let abb: FixedArray<FixedArray<bigint, 2>, 3> = [[1n, 3n], [1n, 3n], [1n, 3n]]359```360 361:::caution362A `FixedArray` behaves differently in an on-chain and off-chain context, when passed as a function argument. It is *passed by reference* off chain, as a regular TypeScript/JavaScript array, while *passed by value* on chain. It is thus strongly recommended to NEVER mutate a `FixedArray` parameter's element inside a function.363 364```ts365class DemoContract extends SmartContract {366 367 @prop(true)368 readonly a: FixedArray<bigint, 3>369 370 constructor(a: FixedArray<bigint, 3>) {371 super(...arguments)372 this.a = a373 }374 375 @method()376 onchainChange(a: FixedArray<bigint, 3>) {377 a[0] = 0378 }379 380 offchainChange(a: FixedArray<bigint, 3>) {381 a[0] = 0382 }383 384 @method()385 public main(a: FixedArray<bigint, 3>) {386 this.onchainChange(this.a)387 // note: a[0] is not changed on chain388 assert(this.a[0] == 1n)389 }390}391 392const arrayA: FixedArray<bigint, 3> = [1n, 2n, 3n]393const instance = new DemoContract(arrayA);394 395instance.offchainChange(arrayA)396// note: arrayA[0] is changed off chain397assert(arrayA[0] = 0n)398```399:::400 401### User-defined Types402 403Users can be define customized types using `type` or `interface`, made of basic types.[^1]404 405```ts406type ST = {407 a: bigint408 b: boolean409}410 411interface ST1 {412 x: ST413 y: ByteString414}415 416type Point = {417 x: number418 y: number419}420 421function printCoord(pt: Point) {422 console.log("The coordinate's x value is " + pt.x)423 console.log("The coordinate's y value is " + pt.y)424}425 426interface Point2 {427 x: number428 y: number429}430 431// Exactly the same as the earlier example432function printCoord(pt: Point2) {433 console.log("The coordinate's x value is " + pt.x)434 console.log("The coordinate's y value is " + pt.y)435}436 437```438 439[^1]: A user-defined type is also passed by value on chain, and by reference off chain, same as a `FixedArray`. It is thus strongly recommended to NEVER mutate the field of a parameter, which is of a user-defined type, inside a function.440 441### Domain Types442 443There are several domain types, specific to the Bitcoin context, used to further improve type safety. They are all subtypes of `ByteString`. That is, they can be used where a `ByteString` is expected, but not vice versa.444 445 446* `PubKey` - a public key447 448* `Sig` - a signature type in [DER format](https://academy.bit2me.com/en/que-son-firmas-estrictas-der), including sighash flags at the end449 450* `Ripemd160` - a RIPEMD-160 hash451 452* `PubKeyHash` - an alias for `Ripemd160`, usually representing a bitcoin address.453 454* `Sha1` - a SHA-1 hash455 456* `Sha256` - a SHA-256 hash457 458* `SigHashType` - a sighash459 460* `SigHashPreimage` - a sighash preimage461 462* `OpCodeType` - a Script [opcode](https://wiki.bitcoinsv.io/index.php/Opcodes_used_in_Bitcoin_Script)463 464```ts465@method()466public unlock(sig: Sig, pubkey: PubKey) {467 // hash160() takes a ByteString as input, but can accept pubkey here, which if of type PubKey468 assert(hash160(pubkey) == this.pubKeyHash)469 assert(this.checkSig(sig, pubkey), 'signature check failed')470}471```472 473## Statements474 475There are some constraints on these following statements within `@method`s, except [variable declarations](#Variable-declarations).476 477### Variable declarations478 479Variables can be declared in `@method`s by keywords `const` / `var` / `let`, like in normal TypeScript.480 481```ts482let a : bigint = 1n483var b: boolean = false484const byte: ByteString = toByteString("ff")485```486 487### `for`488 489Bitcoin does not allow unbounded loops for security reasons, to prevent DoS attacks. All loops must be bounded at compile time. So if you want to loop inside `@method`, you must strictly use the following format:490 491```ts492for (let $i = 0; $i < $maxLoopCount; $i++) {493 ...494}495```496 497:::note498* the initial value must be `0` or `0n`, the operator `<` (no `<=`), and increment `$i++` (no pre-increment `++$i`).499* `$maxLoopCount` must be a [CTC](#compile-time-constant).500* `$i` can be arbitrary name, e.g., `i`, `j`, or `k`. It can be both a `number` or a `bigint` type.501* `break` and `continue` are currently not allowed, but can be emulated like502:::503 504```ts505// emulate break506let x = 3n507let done = false508for (let i = 0; i < 3; i++) {509 if (!done) {510 x = x * 2n511 if (x >= 8n) {512 done = true513 }514 }515}516```517 518### `return`519 520Due to the lack of native return semantics support in Bitcoin Script, a function currently must end with a `return` statement and it is the only valid place for a `return` statement. This requirement may be relaxed in the future.521 522```ts523@method() m(x: bigint): bigint {524 if (x > 2n) return x // invalid525 return x + 1n // valid526}527```528 529This is usually not a problem since it can be circumvented as follows:530```ts531@method()532abs(a: bigint): bigint {533 if (a > 0) {534 return a535 } else {536 return -a537 }538}539```540can be rewritten as541```ts542@method()543abs(a: bigint): bigint {544 let ret : bigint = 0545 546 if (a > 0) {547 ret = a548 } else {549 ret = -a550 }551 return ret552}553```554 555## Compile-time Constant556 557A compile-time constant, CTC for short, is a special variable whose value can be determined at compile time. A CTC must be defined in one of the following ways.558 559 560* A number literal like:561 562```ts5633564```565 566* A `const` variable, whose value must be a numeric literal. Expressions cannot be used for now.567 568```ts569const N1 = 3 // valid570const N2: number = 3 // invalid, no explicit type `number` allowed571const N3 = 3 + 3 // invalid, no expression allowed572```573 574* A `static` `readonly` property:575 576```ts577class X {578 static readonly M1 = 3 // valid579 static readonly M2: number = 3 // invalid580 static readonly M3 = 3 + 3 // invalid581}582```583 584 585A CTC is required in these cases.586 587* Array size588 589```ts590let arr1: FixedArray<bigint, 3> = [1n, 2n, 3n]591// `typeof` is needed since FixedArray takes a type as the array size, not a value592let arr1: FixedArray<bigint, typeof N1> = [1n, 2n, 3n]593let arr2: FixedArray<bigint, typeof X.M1> = [1n, 2n, 3n]594```595 596* Loop count in `for` statement597 598```ts599for(let i=0; i< 3; i++) {}600for(let i=0; i< N1; i++) {}601for(let i=0; i< X.M1; i++) {}602```603 604## Functions605 606### Built-in Functions607You can refer to [Built-ins](./built-ins.md) for a full list of functions and libraries built into `scryptTS`.608 609### Whitelisted Functions610Be default, all Javascript/TypeScript built-in functions and global variables are not allowed in `@method`s, except the following kinds.611 612#### `console.log`613 614`console.log` can be used for debugging purposes.615```ts616@method()617add(x0: bigint, x1:bigint) : bigint {618 console.log(x0)619 return x0 + x1620}621```622 623 624## Operators625 626**sCrypt** is a subset of TypeScript. Only the following operators can be used directly.627 628 629| Operator | Description |630| :-----| :----: |631| `+` | Addition |632| `-` | Subtraction |633| `*` | Multiplication |634| `/` | Division |635| `%` | Remainder |636| `++` | Increment |637| `--` | Decrement |638| `==` | Equal to |639| `!=` | Not equal to |640| `===` | Same as `==` |641| `!==` | Same as `!=` |642| `>` | Greater than |643| `>=` | Greater than or equal to |644| `<` | Less than |645| `<=` | Less than or equal to |646| `&&` | Logical AND |647| <code>||</code> | Logical OR |648| `!` | Logical NOT |649| `cond ? expr1 : expr2 ` | ternary |650| `+=` | Add and assign |651| `-=` | Subtract and assign |652| `*=` | Multiply and assign |653| `/=` | Divide and assign |654| `%=` | Assign remainder |655 656:::note657`**` is not supported currently.658:::