How to Replay a Contract Instance to the Latest State
Using sCrypt Service and sCrypt client, we can effortlessly create a contract instance reflecting the latest state as follows:
const currentInstance = await Scrypt.contractApi.getLatestInstance(
Counter,
contractId
)
However, this method is ineffective for smart contracts with states of type HashedMap or HashedSet. This is because each instance only contains hashed values, not the original ones.
In this section, we'll use contract CrowdfundReplay located at src/contracts/crowdfundReplay.ts
as a reference to explain how to replay these contract instances to their latest states.
This crowdfund contract features a HashedMap donators
that records the donors' public key and their respective donation satoshi amounts.
export type Donators = HashedMap<PubKey, bigint>
export class CrowdfundReplay extends SmartContract {
@prop(true)
donators: Donators
...
}
This contract has three public methods:
donate
adds an entry to the HashedMap.refund
removes a specific donator from the map.collect
destroys the contract without updating any stateful properties.
export type Donators = HashedMap<PubKey, bigint>
export class CrowdfundReplay extends SmartContract {
...
@method()
public donate(donator: PubKey, amount: bigint) {
...
assert(!this.donators.has(donator), 'donator already exists')
this.donators.set(donator, amount)
...
}
@method()
public refund(donator: PubKey, amount: bigint, sig: Sig) {
...
assert(this.donators.canGet(donator, amount), 'not donated before')
assert(this.donators.delete(donator), 'failed to remove donator')
...
}
@method()
public collect(sig: Sig) {
...
}
}
To replay the contract instance to the latest states, follow these three steps:
Step 1. Offchain Helper Functions
Initially, add helper functions that update stateful properties in a manner identical to the public methods.
These functions are defined within the offchainUpdates
object:
class CrowdfundReplay extends SmartContract {
...
offchainUpdates: OffchainUpdates<CrowdfundReplay> = {
'donate': (next: CrowdfundReplay, donator: PubKey, amount: bigint) => {
next.donators.set(donator, amount)
},
'refund': (next: CrowdfundReplay, donator: PubKey) => {
next.donators.delete(donator)
},
}
...
}
The object keys must match the public method names precisely.
In our example, we only need two helper functions since the collect
method doesn't alter any stateful properties.
Step 2. Create Instance from Deployment Tx
Retrieve the deployment transaction using the contract ID. Subsequently, recover the contract instance from it.
// Recover instance from the deployment transaction
const tx = await provider.getTx(contractId.txId)
const instance = CrowdfundReplay.fromTx(
tx,
contractId.outputIndex,
{
donators: new HashedMap<PubKey, bigint>(),
}
)
Note: For more details on the workings of the fromTx()
and getTransaction()
functions, refer to the documentation here.
Step 3. Replay Instance to Latest States
Invoke the replayToLatest
function to acquire the latest contract instance.
import { replayToLatest } from 'scrypt-ts'
...
const latestInstance = await replayToLatest(instance, contractId)
if (latestInstance) {
// The latest instance is now ready for use.
...
}
Note: If the replayToLatest()
function yields null
, it indicates that there have been no state changes for the contract instance. This scenario arises if the contract hasn't been interacted with since its deployment or if all state modifications have been reverted.