Code

From RunWiki
Revision as of 22:08, 28 April 2023 by Zhell (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

With Run, you can own code, like classes and functions, just as easily as you can own objects. This may sound a little meta at first, but don't worry — you've been doing it all along! Every jig you created was linked to a jig class which you owned too. That jig class lived in its own output and had its own location.

When you own a piece of code, it means you have control over that behavior. For example, if you own a jig class, you get to decide what its jig instances are able to do. You can update or destroy that class and even call methods on it. Run calls classes and functions that people own Code with a capital C. Using Code, you can define new kinds of jigs, create reusable helper functions, share your server logic for auditability, discover algorithms from other apps, and much more.

There are two kinds of Code that Run supports:

  1. Jig classes: Jig classes are classes that extend from Jig and behave like jigs. You already know these. You can use them to create jig objects as we've already seen, but you can also call methods on them to update their properties over time. Jig classes evolve according to same Ownership Rules as jig objects.
  2. Sidekicks: Sidekicks are your helpers. They are your classes or functions that don't extend from Jig which you use more for supporting roles. You might use sidekicks to create a custom lock, to define a berry, to make a shared helper like expect, or even to store your server code on-chain. When you call a method on a sidekick, nothing gets recorded to Bitcoin. But you can call sidekicks from inside jigs and even from your own app. There are no restrictions on what kinds of parameters can be passed into or returned from sidekicks. Read more about Sidekicks here.

Using jig classes and sidekicks, you have a powerful repertoire to build extensible apps.

Deploying

You deploy your classes and functions to Bitcoin. Deploying uploads your code in a Bitcoin transaction and creates a new output for it that you own. To deploy, you call run.deploy() and pass in your local class or function. Run will publish the transaction in the background and make you the owner of the new Code. Being the owner allows you to perform actions on it. Code is not necessarily static, as we'll see.

run.deploy() returns to you a new copy of your class or function that you should use going forward. This copy is special: unlike your original code, this copy is linked to your new Bitcoin output. It is also securely sandboxed and acquires several special Code methods that you can use to update it. We suggest adding the word Code to the name of the copy to differentiate it from your local class. You can call sync() on it right away to make sure that it deployed successfully.

We recommend you deploy all of your code in advance and load your load when your app starts. This way, your app and your users have a stable library to work with. You can do this by writing a script outside of your app.

Deploy a sidekick function

function sha256(data) { ... }

const sha256Code = run.deploy(sha256)

await sha256Code.sync()

Loading

You will want to load the code your deployed when your app starts. run.load(), in addition to loading jigs, is able to download, install and sandbox your classes and functions from the blockchain too. Just pass in the location of the code you wish to download.

Load a Data class

const Data = await run.load('<class-location-goes-here>')

const data = new Data('abc')

Alternatively, you can use the inventory to find your code. run.inventory.sync() will load all the code that you own and place it in the run.inventory.code array. You can filter that array by origin to find the code you plan to use.

Warning: Never search for code in your inventory by its name. Anyone can create and send you code with any name.

Find a class you own in the inventory

await run.inventory.sync()

const MyClass = run.inventory.code.find(C => C.origin === myClassOrigin)

Linking

You can create code on Bitcoin that uses other code on Bitcoin, just like you would in your application. To do this, you have to explicitely tell Run that you are using other classes or functions, because Run needs to be able to link the code together.

You tell Run about other code you use by setting the deps property on your class or function. The deps property should be set to an object having all of the code you use inside. Those dependencies stored in deps will be made available as globals inside your class or function.

Deploy a jig class with a dependency

class Reward extends Jig { ... }

class LootBox extends Jig {
  init() { this.reward = new Reward() }
}

LootBox.deps = { Reward }

run.deploy(LootBox)

Extending

You can create hierarchies of jig classes using Run. This is often useful for adding or changing some base class behavior. For example, think of a game where a base Vehicle class is extended to create many types of vehicles, like planes and trains.

To extend a class, you use the extends keyword as you normally would in JavaScript. When you deploy an extension class, Run will automatically create separate outputs for both the parent and the child and link them together. You do not need to add the parent class to the deps object on the child. Run is smart enough to automatically find the parent.

class EditablePost extends Post {
  edit(message) {
    this.message = message
  }
}

Inside methods on the child class, you can call the parent class's methods by using the super keyword in JavaScript. This is useful when you want to override a method on the parent to add behavior but you still want to call the parent method inside.

By default, extending a class requires the parent class's approval. This ensures the instanceof keyword is secure, because if Run didn't require parent class approval, third-parties could extend from a class to trick your instanceof checks into passing. Sometimes, however, you will want third-parties to extend your classes, and in those cases, set the sealed property on your class to false before you deploy so that anyone can create extensions.


Syncing

When you call MyClass.sync(), it works just like myJig.sync(). If there are pending transactions, they will be published to the blockchain, and if there are network updates you haven't received, Run will download and apply them. We always recommend calling sync() after every code update to catch errors too, just like jigs. If you load a class by its origin, it is best to call sync() right after to make sure you have the latest state. For more about syncing, see Jigs: Syncing.

Load a class at its origin and fast-forward it to its latest state


const DigitalPet = await run.load('<digital-pet-class-origin>')

await DigitalPet.sync()

Updating

You can call methods on jig classes just like jig objects. Class methods can change properties, call other class methods, and create instances. You can even send the code to someone else by changing its owner!

To write a jig class method, put static before the method name and it will apply to the class itself and not instances. When you call a class method, Run publishes a Bitcoin transaction with the update and assigns the code to a new location. Other jigs that use the code will see the updates when they sync.

You should think of code as living objects with their own history. Although only jig classes can have static methods that update their state, all code including sidekicks can be upgraded, destroyed, and authed.


class Weapon extends Jig {
  static setPower(power) {
    this.power = power
  }
}

const WeaponCode = run.deploy(Weapon)

WeaponCode.setPower(100)

// WeaponCode.power === 100

Upgrading

After your code is deployed, you may discover that it is missing an important feature or perhaps has a bug. On other blockchains, this is a terminal failure, but in Run, you can call upgrade() on your code and pass in a replacement class or function. That replacement will become the new behavior. Existing jigs that were deployed using your old class will get the upgrade when they sync or when they interact with another jig that already has the upgrade.

To upgrade a class:

Load the old class Write the new class, including all existing methods Copy over existing state to the new class, except the bindings Call OldClass.upgrade(NewClass) and sync We recommend writing a script to upgrade, similar to deploying code. It is important when you upgrade to copy over every property and method to the new class, because you are defining a full replacement. The example on the right shows a safe way to copy over state.

After you upgrade a jig class, you'll want to sync its jig instances when you load them in your app to make sure they get the upgrade. One way to do this is to call jig.constructor.sync() after loading any jig, but if there are many updates this might not be very efficient. Instead, we recommend calling Run.util.unify(jig, LatestClass). This will instantly upgrade the jig to use the latest version of a class that you already know about.

If you wish to provably prevent upgrades, you can set upgradable to false on the class. This is useful if you wish to show others that your code will not fundamentally change.

With great power comes great responsibility! Be careful.

Warning: Upgrades cannot be forced onto existing jigs without their owner's permission. If you discover a critical bug, upgrading may not always be an option and you may have to reissue jigs.
const OldWeapon = await run.load('<weapon-class-location>')

const NewWeapon = class Weapon extends Jig {
  ...
}

// Copy over existing state
Object.assign(NewWeapon, OldWeapon)

// Except the bindings, which are set by Run
['origin', 'location', 'nonce', 'owner', 'satoshis'].forEach(x => { delete NewWeapon[x] })

OldWeapon.upgrade(NewWeapon)

await OldWeapon.sync()

Trusting

Code needs to be trusted in order for Run to load it. To load a jig, its class has to be trusted too, since that defines its behavior. This keeps your application secure by ensuring you do not accidentally load harmful code. All code that Run executes, includes deployed classes, upgraded classes, and any linked code you used in deps, needs to be trusted.

To trust code, you call run.trust() and pass in the transaction ID where the code was deployed or upgraded. Be careful to just pass the transaction ID and not the code's location. If you try to load a jig but not all of its code is trusted, you may see an error saying "Cannot load untrusted code". You can get the untrusted txid out of the error object, trust it if it's safe, and retry the load again.

It is possible to trust any code in your Cache by passing "state" into run.trust(). This is a good idea when you are building the cache yourself because it will lead to better performance.

It is also possible to trust all code by passing "*" into run.trust(). This is strongly discouraged for production applications, but it can be useful during development and testing.

// Trust the class
run.trust('<your-class-origin-txid>')

// Trust an upgrade
run.trust('<your-class-upgrade-location-txid>')

Where to go from here

Now that you understand the basics of Codes, let's see how to use them to create Tokens