Skip to content

Commit d79ada7

Browse files
authored
Merge pull request #284 from sCrypt-Inc/stateful
Stateful
2 parents 316416f + 328aa6c commit d79ada7

File tree

116 files changed

+14317
-72
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

116 files changed

+14317
-72
lines changed

btc-docs/advanced/_category_.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"position": 16,
3+
"label": "Advanced",
4+
"collapsible": true,
5+
"collapsed": true
6+
}

btc-docs/advanced/how-to-debug-scriptcontext.md

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,19 @@ When it runs incorrectly, you need to master the following methods to locate the
1212

1313
## hashOutputs assertion failed
1414

15-
The `shaOutputs` field of `ScriptContext` is the double SHA256 of the serialization of all output amount (8-byte little endian) with scriptPubKey. Through it, we can agree on how the outputs of the transaction calling the contract should be constructed.
15+
The `shaOutputs` field of `ScriptContext` is the SHA256 of the serialization of all output amount (8-byte little endian) with scriptPubKey. Through it, we can agree on how the outputs of the transaction calling the contract should be constructed.
1616

17-
If the output of the transaction is not constructed as required by the contract, then the `shaOutputs` of `ScriptContext` field will not match the the double SHA256 of the `outputs` produced in the code when the contract runs. The following assertion will fail:
17+
If the output of the transaction is not constructed as required by the contract, then the `shaOutputs` of `ScriptContext` field will not match the the SHA256 of the `outputs` produced in the code when the contract runs. The following assertion will fail:
1818

1919
```ts
20-
assert(this.ctx.shaOutputs == sha256(outputs), 'shaOutputs mismatch')
20+
assert(this.checkOutputs(outputs), 'mismatch outputs');
2121
```
22+
The code above is equivalent to the following:
23+
24+
```ts
25+
assert(sha256(outputs) === this.ctx.shaOutputs, `outputs hash mismatch`);
26+
```
27+
2228

2329
We all know that if the preimage of the hash is inconsistent, the hash value will not match. When an assertion failure occurs, we can only see two mismatched hash values, and cannot visually see the difference between the preimages of the two hash values (that is, the `outputs` in the contract and the outputs of the transaction).
2430

@@ -29,18 +35,17 @@ Just call `this.debug.diffOutputs(outputs)` in the contract:
2935

3036
```ts
3137
this.debug.diffOutputs(outputs) // diff and print the comparison result
32-
assert(this.ctx.shaOutputs == sha256(outputs), 'shaOutputs mismatch')
38+
assert(this.checkOutputs(outputs), 'mismatch outputs');
3339
```
3440

3541
and you will see the comparison result:
3642

37-
![diffoutputs](../../static/img/diffoutputs.png)
43+
![diffoutputs](../../static/img/diffoutputs-btc.png)
3844

3945

4046
If the outputs of the transaction is inconsistent with the outputs expected by the contract:
4147

42-
1. Outputs of the transaction is marked green.
43-
2. Outputs expected by the contract is marked red.
44-
3. Identical parts are marked in gray.
48+
1. Outputs of the transaction are tagged with `#context`.
49+
2. Outputs expected by the contract are tagged with `#contract`.
4550

4651
Through the printed comparison results, we can intuitively see that the number of satoshis included in the output calculated in the contract is different from the number of satoshis included in the output actually added when constructing the transaction. Now, we have found the source of the error.

btc-docs/how-to-debug-a-contract.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,8 @@ You can use VS Code to debug sCrypt contracts, the same way as any other TypeScr
5757
## Debug a ScriptContext Failure
5858
One common failure is caused by IContext assertions, like
5959
```typescript
60-
assert(this.ctx.shaOutputs == sha256(outputs), 'shaOutputs mismatch')
60+
assert(this.checkOutputs(outputs), 'mismatch outputs');
6161
```
62+
63+
Refer to [this guide](advanced/how-to-debug-scriptcontext.md) to debug such failures.
64+
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
---
2+
sidebar_position: 5
3+
---
4+
5+
# Interact with a Deployed Contract
6+
7+
## Overview
8+
In this tutorial, we will interact with a deployed smart contract by calling its public method, in a separate process or by a different party.
9+
10+
To do this, we need to create a smart contract instance that corresponds to the deployed contract on chain.
11+
12+
## The Smart Contract
13+
14+
We will reuse the stateful `Counter` contract [from a previous step](../how-to-write-a-contract/stateful-contract#create-a-stateful-contract).
15+
16+
```ts
17+
18+
export interface CounterState extends StructObject {
19+
count: Int32;
20+
}
21+
22+
export class Counter extends SmartContract<CounterState> {
23+
@method()
24+
public increase() {
25+
this.state.count++;
26+
27+
this.appendStateOutput(
28+
// new output of the contract
29+
TxUtils.buildOutput(this.ctx.spentScript, this.ctx.spentAmount),
30+
// new state hash of the contract
31+
Counter.stateHash(this.state),
32+
);
33+
34+
const outputs = this.buildStateOutputs() + this.buildChangeOutput();
35+
36+
assert(this.checkOutputs(outputs), 'Outputs mismatch with the transaction context')
37+
}
38+
}
39+
```
40+
41+
## Deploy
42+
43+
To deploy the smart contract by calling the `deplly()` function:
44+
45+
```ts
46+
const counter = new Counter();
47+
counter.state = {
48+
count: 0n
49+
}
50+
const covenant = StatefulCovenant.createCovenant(counter)
51+
const provider = getDefaultProvider()
52+
const signer = getDefaultSigner()
53+
const deployTx = await deploy(signer, provider, covenant)
54+
console.log(`Counter deployed: ${deployTx.id}, the count is: ${counter.state.count}`)
55+
```
56+
57+
The function deploys the contract with a initial state `0n` and returns the transaction of the deployed contract.
58+
59+
## Interact with call feature
60+
61+
Next, we update our deployed smart contract by calling the `call()` function:
62+
63+
```ts
64+
const newCovenant = covenant.next({ count: covenant.state.count + 1n });
65+
66+
const callTx = await call(signer, provider, covenant, {
67+
invokeMethod: (contract: Counter) => {
68+
contract.increase()
69+
},
70+
}, {
71+
covenant: newCovenant,
72+
satoshis: 330
73+
})
74+
75+
76+
console.log(`Counter contract called: ${callTx.getId()}`)
77+
covenant = newCovenant;
78+
```
79+
80+
This function will help you construct transactions to call the covenant. It takes *5* parameters:
81+
82+
83+
1. a [signer](../how-to-deploy-and-call-a-contract#signer) - *required*
84+
2. a [provider](../how-to-deploy-and-call-a-contract#provider) - *required*
85+
3. a covenant to be called - *required*
86+
4. a subContractCall - used to specify which contract to use for unlocking - *required*
87+
5. a new covenant which can be get by `covenant.next(new state)` and its locked satoshis, - *optional*
88+
89+
90+
Let's encapsulate the entire process within a main function, designed to deploy the contract and increment its value five times:
91+
92+
```ts
93+
async function main() {
94+
95+
const counter = new Counter();
96+
counter.state = {
97+
count: 0n
98+
}
99+
100+
let covenant = StatefulCovenant.createCovenant(counter)
101+
102+
const provider = getDefaultProvider()
103+
const signer = getDefaultSigner()
104+
105+
const deployTx = await deploy(signer, provider, covenant)
106+
107+
console.log(`Counter contract deployed: ${deployTx.getId()}`)
108+
109+
110+
for (let i = 0; i < 10; i++) {
111+
const newCovenant = covenant.next({ count: covenant.state.count + 1n });
112+
113+
await sleep(5)
114+
115+
const callTx = await call(signer, provider, covenant, {
116+
invokeMethod: (contract: Counter) => {
117+
contract.increase()
118+
},
119+
}, {
120+
covenant: newCovenant,
121+
satoshis: 330
122+
})
123+
124+
125+
console.log(`Counter contract called: ${callTx.getId()}`)
126+
covenant = newCovenant;
127+
}
128+
}
129+
130+
(async () => {
131+
await main()
132+
})()
133+
```
134+
135+
If we execute the code, we should get an output similar to the following:
136+
137+
```ts
138+
Counter deployed: 1b1c09383f6a483dabf138111cf80e6921cc2050ffdbb6c7493f47a2c3759180, the count is: 0
139+
Counter covenant called: ec07e45c9114ce2b4f439414f0c79e13e90e3157f6dae6c1e66510d7f2cecc6c, the count now is: 1
140+
Counter covenant called: f65967b0cfe84e7e8c7b54bda2ce6f216177f87bbc95561044470321f435c07c, the count now is: 2
141+
Counter covenant called: e4f0862c62a6c42e10097c0e1b975710f3a4ef0f768a694e5edc7c4bd20997eb, the count now is: 3
142+
Counter covenant called: 68da33af2c64657de41dfcd9c525f3f8c37cffd28be8a4a5374bc8ea31e8f7b5, the count now is: 4
143+
Counter covenant called: 24033524f0bceb18172f9bbb4c3baecdcf8f04e233bd13ed27c9061e0f224d4d, the count now is: 5
144+
```
145+
146+
## Interact with customize transaction
147+
148+
For more complex covenants, you may need to build your own transaction to complete the call to covenant. The following code shows how to construct a transaction to call the `Counter` covenant:
149+
150+
```ts
151+
const contract = new Counter()
152+
contract.state = { count: 0n };
153+
const covenant = StatefulCovenant.createCovenant(contract)
154+
const provider = getDefaultProvider()
155+
const signer = getDefaultSigner()
156+
const deployTx = await deploy(signer, provider, covenant)
157+
console.log(`Counter deployed: ${deployTx.id}, the count is: ${contract.state.count}`)
158+
159+
160+
// create a new covenant containing new states
161+
const newCovenant = covenant.next({ count: covenant.state.count + 1n });
162+
163+
const address = await signer.getAddress();
164+
165+
const feeRate = await provider.getFeeRate();
166+
167+
// fetch utxos to pay fee
168+
const utxos = await provider.getUtxos(address);
169+
170+
// build transaction
171+
const psbt = new ExtPsbt().addCovenantInput(covenant).spendUTXO(utxos);
172+
173+
// add Counter covenant output
174+
psbt.addCovenantOutput(newCovenant, 330);
175+
176+
// update unlocking script
177+
psbt.updateCovenantInput(0, covenant, {
178+
invokeMethod: (contract: Counter) => {
179+
contract.increase()
180+
},
181+
});
182+
183+
// add change output
184+
psbt.change(address, feeRate).seal();
185+
186+
// get sign options
187+
const options = psbt.psbtOptions() || {
188+
autoFinalized: false,
189+
toSignInputs: [],
190+
};
191+
192+
utxos.forEach((_, index) => {
193+
options.toSignInputs.push({
194+
index: index + 1,
195+
address: address,
196+
});
197+
});
198+
199+
// request sign
200+
const signedPsbtHex = await signer.signPsbt(psbt.toHex(), options);
201+
// combine signature
202+
const signedPsbt = psbt.combine(ExtPsbt.fromHex(signedPsbtHex)).finalizeAllInputs();
203+
// extract psbt to Transaction
204+
const callTx = signedPsbt.extractTransaction();
205+
// broadcast transaction to finish the call
206+
await provider.broadcast(callTx.toHex());
207+
208+
console.log(`Counter covenant called: ${callTx.getId()}`)
209+
```
210+
211+
The above code uses `ExtPsbt` to build transactions. `ExtPsbt` is an extension class of [Psbt](../references/bitcoinjs-lib/classes/Psbt).
212+
213+
`Psbt` class can parse and generate a PSBT binary based off of the BIP174.
214+
215+
## Conclusion
216+
217+
Congratulations! You've now deployed AND interacted with a Bitcoin smart contract.

btc-docs/how-to-deploy-and-call-a-contract/deploy-cli.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ Here's an example of such a deployment file:
2929
import { Demo } from './src/contracts/demo'
3030
import * as dotenv from 'dotenv'
3131
import { getDefaultProvider, getDefaultSigner } from './tests/utils/txHelper';
32-
import { readArtifact } from '@scrypt-inc/scrypt-ts-transpiler-btc';
3332
import { Covenant, deploy, sha256, toByteString } from '@scrypt-inc/scrypt-ts-btc';
3433

3534
import * as dotenv from 'dotenv'
@@ -38,8 +37,6 @@ import * as dotenv from 'dotenv'
3837
dotenv.config()
3938

4039
async function main() {
41-
const artifact = readArtifact(Demo);
42-
Demo.loadArtifact(artifact)
4340
const covenant = Covenant.createCovenant(new Demo(sha256(toByteString("hello world", true))))
4441

4542
const provider = getDefaultProvider();

0 commit comments

Comments
 (0)