ErgoScript Design patterns

Based on the recent posts, I thought I’d start this thread listing the common design patterns we have seen in ErgoScript use-cases.

  1. Singleton Token: Essentially a token which exists as a singleton (i.e., when token was issued in quantity one). The box in which token is stored can have other sophisticated scripts (see below).
  2. Perpetual Token: This is a singleton token that cannot be destroyed (i.e., the box in which it is stored has a script that requires the spending transaction to output a box with identical contents). This is described in the post “A perpetual token”.
  3. One-use-per-block Token: This is a perpetual token that can be spent at most once per block. In other words, the box containing this token must have at least one confirmation to be spent. This is described by @jasondavies in this blog post.
  4. Cyclic References: This is when ScriptA requires an output box to be protected by some guard ScriptB, and ScriptB requires an output box to be protected by ScriptA. The solution is discussed in this forum post.
  5. Prove that a certain unspent box exists: This is done by including that box as data-input to a transaction.
  6. Prove that out of two boxes, the other one has been spent. This is a special type of non-existence proof that can be done via tokens as explained in the paragraph “Identifying the second spender” in Section 4.4 of the ZeroJoin paper.
  7. Requiring that the tx can only be mined by a particular user (Alice): This is done by adding the condition minerPubKey == alice, as discussed in this post. The credit for discovering the attack (for which this pattern was created) goes to one of the core Ergo developers (unfortunately, I cannot remember who and those logs are lost in #Slack somewhere).
  8. Requiring that the transaction cannot be mined after a certain deadline has passed. The script contains the additional clause that HEIGHT < getVar[Int](1).get, which ensures that the transaction cannot be mined if the block height crosses the value in the first context variable (provided by the spender). This is quite a simple pattern but quite useful, and first appeared in the Advanced ErgoScript Tutorial, Section 2.1.
  9. Generalizing above pattern, instead of hard-wiring the condition HEIGHT < getVar[Int](1).get, we could specify this (and any other) script at run-time, as described in this thread.

In case I have missed crediting anyone, please mention and I’ll update the post.

I have definitely missed many other interesting patterns currently known, and surely there are many more to be discovered. Hopefully others will add to the list (and I’ll update the OP).

7 Likes

One more useful pattern, and part of Ergo from day 1 is the “emission box” pattern, used in emitting mining rewards. All the ergs to be ever emitted are locked in the emission box, and mining rewards are taken by unlocking certain amount of ergs and keeping the balance in a new copy of the destroyed emission box.

The idea of emission boxes is quite general and need not be used only for rewards. For instance, it has been reused in the “fee-emission” boxes in ErgoMix.

4 Likes

Very generic meta-pattern, I guess, is “check the work done externally, instead of doing it in the script”.

As a particular pattern example, let’s consider the sorting pattern. Assume that inputs contain e.g. DEX orders (or oracle data), and they are needed to be sorted in the script (for matching in case of DEX, or removing extreme values in case of oracles). Then, instead of doing sorting in the script (expensive), the script checks for inputs to be sorted.

2 Likes

More so meta design-patterns which pertains to the blockchain context (storage rent), rather than just ErgoScript, but:

  1. When bootstrapping a box/contract that is expected to live on-chain for a long duration of time, it makes sense to include an extra “buffer” of Erg held by the box in case it lasts longer than the storage-rent timer.

  2. Including a spending path to all long-lasting contracts (or for example boxes intended to hold data indefinitely) which allows users to “top-up” Erg into the box to prevent it being garbage collected by storage-rent. This spending path stipulates that all data/tokens/script must be equivalent in a single new output box, but tx can include extra input boxes which only hold Erg and as a result increase the total Erg held by the newly created box (while at the same time refreshing the storage-rent timer for one iteration).

4 Likes

Another design pattern that I think will be very common in Ergo’s model is parallelizing actions in separate UTXOs and collecting all of the data/tokens together as inputs in a tx which folds the data/tokens down into one (or potentially more depending on the protocol) box.

This is in contrast to having 1 box that holds data pertaining to a dApp/protocol which everyone must sequentially spend in order to add their piece of data to. Instead all users can create their own box with the data in the registers (which can happen parallelized in the same block or across multiple) and once all have posted, then an accumulation transaction can be created which consumes all of them and produces a final box that has all of the data/tokens.

This is in essence how data accumulation pools work but this is a general design pattern that allows for building more efficient dApp protocols that have the potential to scale (and potentially just work better for certain use cases).

2 Likes

This is probably so well-known it doesn’t really warrant a note, but just in case, the commit/reveal pattern is useful for many contracts.

In the context of prizes for solving puzzles, e.g. in Mathematical Fun with ErgoScript, it is best to require that a commitment to the solution is mined first before the solution is revealed. Without a commitment, revealing the solution in an unconfirmed transaction is vulnerable to theft because the solution can be copied into a new transaction, and the reward sent somewhere else, assuming it gets mined.

3 Likes

In other examples, Namecoin uses this to prevent “front-running” attacks, where someone picks a domain and a miner steals that. And “rock papers scissors” in Ethereum and Ergo also use this to prevent second player from cheating.

It may be well known but people still fail to take this into account (see Section 4.2 attached paper).
Hence, good to explicitly list it out.

4 Likes

Another useful design pattern (developed upon ideas proposed by @robert) is the following.

To check that some given box (not necessarily SELF) was created after a certain height h:
Check that box.creationHeight > h (creation height is stored in in R3 and the protocol requires that creationHeight be <= actual height when the box is mined).

It seems more tricky to check that some given box was created before some height h. Probably there is some solution in ErgoScript which I have missed.

3 Likes

Another useful design pattern is to emulate executeFromVar or executeFromRegister in ErgoScript.

Currently ErgoScript does not support the above two instructions but ErgoTree does.

executeFromVar allows the box spender to specify some arbitrary script in a context var that will be executed.

executeFromRegister allows the box creator to specify some arbitrary script in a register of that box, which will be executed later on when the box is spent.

In order to emulate this in ErgoScript until it is supported at a later time, we can use the following logic:

If a box is given as part of the INPUTS array, the script in it will be executed
(Note: if a box is given as part of the DATA-INPUTS array, the script in it will not be executed)

Let us look at executeFromRegister for some given box B where we want to execute the script in R4. Instead of the script, this box will now contain a hash of the script for compactness.

The condition of the script in B will be

blake2b256(INPUTS(1).propositonBytes)) == SELF.R4[Coll[Byte]].get

2 Likes

Some contracts might have some kind of “daily withdrawal limit”. This is easily implemented by keeping track of the remaining limit within the current 24-hour period (which can be based on block height i.e. every 720 blocks). Once a new period starts, the limit can be reset.

3 Likes

Emulating context variables using registers:

In some situations, context variables may not be possible (for example, currently Appkit does not support them). We can achieve the same thing by encoding the data in context variables in a register of one of the outputs. For example, suppose we have the code

val x = getVar(0)[Int].get

This can be emulated by creating a new output box (or reusing an existing one) at some index, say 1, and storing that data in its register R4. Then the above code can be replaced by

val x = OUTPUTS(1).R4[Int].get

1 Like

To back up @scalahub’s point, we now have an example in the “ErgoScript By Example” that shows how to do this. Originally the Pin Lock script used a context variable, but then was switched to using R4 of the output: https://github.com/ergoplatform/ergoscript-by-example/blob/main/pinLockContract.md

Now that we have the above repo, I was thinking that we could also try to make many of these design patterns more approachable for new devs by making them into full fledged examples and submitting them to the repo. It’s a bit more effort on each of our parts, but I think it’d really help this become the go-to repo for all devs, and even be a good reference for any EUTXO blockchain potentially as well.

1 Like