Getting Started
This is a tutorial which will demonstrate how easy it is to create a disputable contract using the celeste-helpers package
Developing smart contracts integrating with Celeste
This tutorial assumes you already have node.js, npm and truffle installed. The setup commands are shown for a bash terminal. You may need to adapt some commands for other OS and terminals.
If you already have a truffle project configured (solidity compiler version at a minimum of 0.8.x
) you can jump straight to step 3.
1. Initializing your project
Enter the following commands one by one into your terminal:
This will initialize a new npm/truffle project.
2. Configuring truffle
Open your truffle-config.js
file which should've been generated as the truffle project was initialized and make sure you set the compiler to be at least solidity 0.8.x
. An example config (the default truffle comments have been removed but you may keep as they may prove to be useful):
3. Installing celeste-helpers
celeste-helpers
Going back to your terminal you can easily install the package via npm:
Since one of celeste-helpers
dependencies is @openzeppelin/contracts
you do not need to install it in case you were planning on using the library.
4. Creating a Celeste disputable contract
Now that all the necessary parts are installed you can start writing your contract.
4.1 Disputable contract boilerplate
We'll create a simple employment agreement so we'll name our contract "WorkAgreement". Our contract inherits from the Disputable parent contract provided by celeste-helpers
to facilitate creating a dispute from within our contract.
We make our constructor payable as we want the agreement to hold some funds. To simplify the contract a bit it will only be holding xDai.
4.2 Payment release
To make the contract easy to use and not always requiring a dispute we'll make it optimistic. What this means is that we'll program the contract so that it assumes everything is going well and that the underlying subjective agreement is not being violated. Only if something goes wrong will we ask Celeste to settle the dispute.
In order to leave time for initiating a potential dispute and allow the contractor or employee to fulfil their task we'll allow for a releaseAt
parameter which will be the timestamp at which the contract allows the release of funds.
This is a simple contract so instead of transferring the funds directly to the recipient we can destroy the contract pointing it to the recipient. This is useful as the selfdestruct
operation not only deletes the current contract but also transfers all remaining funds to the address it's pointed to. It also cannot revert unlike a normal transfer.
However, a problem with this code is that anyone can call the releasePayment
method once the releaseAt
timestamp has passed. To prevent someone stealing the funds we'll access restrict the method by introducing a new parameter which will store the recipient, we'll call the variable contractor
:
4.3 Making the payment disputable
Let's create a new method dispute
that allows a dispute to be created with Celeste. There's a few things that have to be considered when creating a dispute:
Who are the conflicting parties (defendant and challenger)?
How will the Celeste dispute creation be payed?
What's the agreement metadata and where will it be stored?
What will your contract do or not do while it is waiting for a ruling?
Considering these aspects for our contract:
It's quite straight forward here: the employer is the one who'd dispute the release of a payment and would thus be the challenger. The contract is the defendant.
We can think of several mechanisms to fairly balance the fee payment for our dispute but we'll say that it's up to the employer to pay the Celeste fee if he wants to dispute the payment.
We'll simply store the agreement data in the contract. To ensure that the employer or contractor can't just change the agreement as they please we'll set it once in the constructor.
While the dispute is being arbitrated in Celeste we'll just prevent the
releasePayment
from being called:
The _prepareAndPullDisputeFeeFrom
pulls the fee from the specified address if it has the necessary allowance and makes sure the Celeste fee can be paid. The _createDisputeAgainst
method then creates the dispute with Celeste.
4.4 Handling the Celeste ruling
Now that our payment is disputable we also want our contract to be able to settle the dispute. In order to do that we also need to store the dispute ID of the dispute we create. We can easily get it as it's returned by _createDisputeAgainst
:
If there is no ruling yet the arbitrator.rule
method will simply revert. The ruling can be one of three states (RULING_REFUSED
, RULING_AGAINST_ACTION
, RULING_FOR_ACTION
). To make our contract simpler we'll give the contractor the benefit of the doubt, so unless Celeste rules explicitly against the release of the payment the contractor will receive the money. However if the court rules against the action the funds in the contract will be returned to the employer.
4.5 Hiding the agreement
While not necessary we can make a small enhancement to our agreement, we can hide the specifics of the agreement. Since the agreement metadata is a memory variable it can be quite gas intensive to write and read from storage. Furthermore the contract doesn't need to know the entire metadata unless a dispute is initiated. If it comes to a dispute the agreement will have to be revealed publicly on the blockchain in order to allow Celeste jurors to rule based on its contents and evidence but initially hiding the contract can give some privacy to the two parties if they are not in a dispute.
In order to hide the contents of the agreement while still have it be immutable and binding we can use the power of cryptographic commitments. While this may sound complicated the concept of commitments is really quite simple. All we do is hash the agreement along with an additional random piece of data (the salt), the resulting hash aka the "commitment" is then submitted to our contract instead of the entire agreement. This is more efficient as regardless of the size of the agreement metadata our contract will only need to store one 32-byte value.
The hash function ensures that nobody can reverse the commitment to retrieve the commitment. The random salt ensures that the same agreement can be reused without the resulting commitment being identical. Thankfully solidity allows us to easily use such cryptography directly in our contracts:
Once the contract is deployed the employer can give the salt along with the agreement to the contractor to allow him to verify that the contract's agreement is what he expects it to be. If the employer wants to dispute the payment he just needs to submit the agreement along with the salt to the contract.
4.6 Further improvements
Further improvements could be made like allowing the agreement to be changed if both parties agree, allowing the contractor to directly return the payment to employer if they want to, using ERC20 tokens for payment, streaming the payment, breaking up the payment into smaller parts fixed to certain goals etc.
5. Testing your contracts
The celeste-helpers
package also provides mocks of Celeste to allow you to easily test your contracts in a local environment without needing to redeploy and configure an entire Celeste instance.
Testing will be covered in the Testing disputable contracts section.
6. Using the court manifest and submitting evidence
By inheriting from the Disputable
parent contract your contract automatically enables relevant participants and their representatives to submit evidence for disputes created using the _createDisputeAgainst
method.
Final Notes
The combination of the immutability of smart contracts and the decentralized subjectivity enabled by Celeste make smart contracts much more useful than they would be on their own.
For a full documentation of the features provided by the celeste-helpers
package and how to use them feel free to refer to the Disputable API and Testing disputable contracts sections.
Last updated