Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions btc-docs/advanced/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"position": 16,
"label": "Advanced",
"collapsible": true,
"collapsed": true
}
21 changes: 13 additions & 8 deletions btc-docs/advanced/how-to-debug-scriptcontext.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,19 @@ When it runs incorrectly, you need to master the following methods to locate the

## hashOutputs assertion failed

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

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:
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:

```ts
assert(this.ctx.shaOutputs == sha256(outputs), 'shaOutputs mismatch')
assert(this.checkOutputs(outputs), 'mismatch outputs');
```
The code above is equivalent to the following:

```ts
assert(sha256(outputs) === this.ctx.shaOutputs, `outputs hash mismatch`);
```


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

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

```ts
this.debug.diffOutputs(outputs) // diff and print the comparison result
assert(this.ctx.shaOutputs == sha256(outputs), 'shaOutputs mismatch')
assert(this.checkOutputs(outputs), 'mismatch outputs');
```

and you will see the comparison result:

![diffoutputs](../../static/img/diffoutputs.png)
![diffoutputs](../../static/img/diffoutputs-btc.png)


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

1. Outputs of the transaction is marked green.
2. Outputs expected by the contract is marked red.
3. Identical parts are marked in gray.
1. Outputs of the transaction are tagged with `#context`.
2. Outputs expected by the contract are tagged with `#contract`.

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.
5 changes: 4 additions & 1 deletion btc-docs/how-to-debug-a-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,8 @@ You can use VS Code to debug sCrypt contracts, the same way as any other TypeScr
## Debug a ScriptContext Failure
One common failure is caused by IContext assertions, like
```typescript
assert(this.ctx.shaOutputs == sha256(outputs), 'shaOutputs mismatch')
assert(this.checkOutputs(outputs), 'mismatch outputs');
```

Refer to [this guide](advanced/how-to-debug-scriptcontext.md) to debug such failures.

217 changes: 217 additions & 0 deletions btc-docs/how-to-deploy-and-call-a-contract/call-deployed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
---
sidebar_position: 5
---

# Interact with a Deployed Contract

## Overview
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.

To do this, we need to create a smart contract instance that corresponds to the deployed contract on chain.

## The Smart Contract

We will reuse the stateful `Counter` contract [from a previous step](../how-to-write-a-contract/stateful-contract#create-a-stateful-contract).

```ts

export interface CounterState extends StructObject {
count: Int32;
}

export class Counter extends SmartContract<CounterState> {
@method()
public increase() {
this.state.count++;

this.appendStateOutput(
// new output of the contract
TxUtils.buildOutput(this.ctx.spentScript, this.ctx.spentAmount),
// new state hash of the contract
Counter.stateHash(this.state),
);

const outputs = this.buildStateOutputs() + this.buildChangeOutput();

assert(this.checkOutputs(outputs), 'Outputs mismatch with the transaction context')
}
}
```

## Deploy

To deploy the smart contract by calling the `deplly()` function:

```ts
const counter = new Counter();
counter.state = {
count: 0n
}
const covenant = StatefulCovenant.createCovenant(counter)
const provider = getDefaultProvider()
const signer = getDefaultSigner()
const deployTx = await deploy(signer, provider, covenant)
console.log(`Counter deployed: ${deployTx.id}, the count is: ${counter.state.count}`)
```

The function deploys the contract with a initial state `0n` and returns the transaction of the deployed contract.

## Interact with call feature

Next, we update our deployed smart contract by calling the `call()` function:

```ts
const newCovenant = covenant.next({ count: covenant.state.count + 1n });

const callTx = await call(signer, provider, covenant, {
invokeMethod: (contract: Counter) => {
contract.increase()
},
}, {
covenant: newCovenant,
satoshis: 330
})


console.log(`Counter contract called: ${callTx.getId()}`)
covenant = newCovenant;
```

This function will help you construct transactions to call the covenant. It takes *5* parameters:


1. a [signer](../how-to-deploy-and-call-a-contract#signer) - *required*
2. a [provider](../how-to-deploy-and-call-a-contract#provider) - *required*
3. a covenant to be called - *required*
4. a subContractCall - used to specify which contract to use for unlocking - *required*
5. a new covenant which can be get by `covenant.next(new state)` and its locked satoshis, - *optional*


Let's encapsulate the entire process within a main function, designed to deploy the contract and increment its value five times:

```ts
async function main() {

const counter = new Counter();
counter.state = {
count: 0n
}

let covenant = StatefulCovenant.createCovenant(counter)

const provider = getDefaultProvider()
const signer = getDefaultSigner()

const deployTx = await deploy(signer, provider, covenant)

console.log(`Counter contract deployed: ${deployTx.getId()}`)


for (let i = 0; i < 10; i++) {
const newCovenant = covenant.next({ count: covenant.state.count + 1n });

await sleep(5)

const callTx = await call(signer, provider, covenant, {
invokeMethod: (contract: Counter) => {
contract.increase()
},
}, {
covenant: newCovenant,
satoshis: 330
})


console.log(`Counter contract called: ${callTx.getId()}`)
covenant = newCovenant;
}
}

(async () => {
await main()
})()
```

If we execute the code, we should get an output similar to the following:

```ts
Counter deployed: 1b1c09383f6a483dabf138111cf80e6921cc2050ffdbb6c7493f47a2c3759180, the count is: 0
Counter covenant called: ec07e45c9114ce2b4f439414f0c79e13e90e3157f6dae6c1e66510d7f2cecc6c, the count now is: 1
Counter covenant called: f65967b0cfe84e7e8c7b54bda2ce6f216177f87bbc95561044470321f435c07c, the count now is: 2
Counter covenant called: e4f0862c62a6c42e10097c0e1b975710f3a4ef0f768a694e5edc7c4bd20997eb, the count now is: 3
Counter covenant called: 68da33af2c64657de41dfcd9c525f3f8c37cffd28be8a4a5374bc8ea31e8f7b5, the count now is: 4
Counter covenant called: 24033524f0bceb18172f9bbb4c3baecdcf8f04e233bd13ed27c9061e0f224d4d, the count now is: 5
```

## Interact with customize transaction

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:

```ts
const contract = new Counter()
contract.state = { count: 0n };
const covenant = StatefulCovenant.createCovenant(contract)
const provider = getDefaultProvider()
const signer = getDefaultSigner()
const deployTx = await deploy(signer, provider, covenant)
console.log(`Counter deployed: ${deployTx.id}, the count is: ${contract.state.count}`)


// create a new covenant containing new states
const newCovenant = covenant.next({ count: covenant.state.count + 1n });

const address = await signer.getAddress();

const feeRate = await provider.getFeeRate();

// fetch utxos to pay fee
const utxos = await provider.getUtxos(address);

// build transaction
const psbt = new ExtPsbt().addCovenantInput(covenant).spendUTXO(utxos);

// add Counter covenant output
psbt.addCovenantOutput(newCovenant, 330);

// update unlocking script
psbt.updateCovenantInput(0, covenant, {
invokeMethod: (contract: Counter) => {
contract.increase()
},
});

// add change output
psbt.change(address, feeRate).seal();

// get sign options
const options = psbt.psbtOptions() || {
autoFinalized: false,
toSignInputs: [],
};

utxos.forEach((_, index) => {
options.toSignInputs.push({
index: index + 1,
address: address,
});
});

// request sign
const signedPsbtHex = await signer.signPsbt(psbt.toHex(), options);
// combine signature
const signedPsbt = psbt.combine(ExtPsbt.fromHex(signedPsbtHex)).finalizeAllInputs();
// extract psbt to Transaction
const callTx = signedPsbt.extractTransaction();
// broadcast transaction to finish the call
await provider.broadcast(callTx.toHex());

console.log(`Counter covenant called: ${callTx.getId()}`)
```

The above code uses `ExtPsbt` to build transactions. `ExtPsbt` is an extension class of [Psbt](../references/bitcoinjs-lib/classes/Psbt).

`Psbt` class can parse and generate a PSBT binary based off of the BIP174.

## Conclusion

Congratulations! You've now deployed AND interacted with a Bitcoin smart contract.
3 changes: 0 additions & 3 deletions btc-docs/how-to-deploy-and-call-a-contract/deploy-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ Here's an example of such a deployment file:
import { Demo } from './src/contracts/demo'
import * as dotenv from 'dotenv'
import { getDefaultProvider, getDefaultSigner } from './tests/utils/txHelper';
import { readArtifact } from '@scrypt-inc/scrypt-ts-transpiler-btc';
import { Covenant, deploy, sha256, toByteString } from '@scrypt-inc/scrypt-ts-btc';

import * as dotenv from 'dotenv'
Expand All @@ -38,8 +37,6 @@ import * as dotenv from 'dotenv'
dotenv.config()

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

const provider = getDefaultProvider();
Expand Down
Loading