Locking by archiving

Pre-condition: there exists a contract that needs to be locked and unlocked. In this section, Coin is used as the original contract to demonstrate locking and unlocking.

template Coin
  with
    owner: Party
    issuer: Party
    amount: Decimal
    delegates : [Party]
  where
    signatory issuer, owner
    observer delegates

    controller owner can

      Transfer : ContractId TransferProposal
        with newOwner: Party
        do   
            create TransferProposal
             with coin=this; newOwner

    --a coin can only be archived by the issuer under the condition that the issuer is the owner of the coin. This ensures the issuer cannot archive coins at will.
    controller issuer can
      Archives
        : ()
        do assert (issuer == owner)

Archiving is a straightforward choice for locking because once a contract is archived, all choices on the contract become unavailable. Archiving can be done either through consuming choice or archiving contract.

Consuming choice

The steps below show how to use a consuming choice in the original contract to achieve locking:

  • Add a consuming choice, Lock, to the Coin template that creates a LockedCoin.
  • The controller party on the Lock may vary depending on business context. In this example, owner is a good choice.
  • The parameters to this choice are also subject to business use case. Normally, it should have at least locking terms (eg. lock expiry time) and a party authorized to unlock.
      Lock : ContractId LockedCoin
        with maturity: Time; locker: Party
        do create LockedCoin with coin=this; maturity; locker
  • Create a LockedCoin to represent Coin in the locked state. LockedCoin has the following characteristics, all in order to be able to recreate the original Coin:

    • The signatories are the same as the original contract.
    • It has all data of Coin, either through having a Coin as a field, or by replicating all data of Coin.
    • It has an Unlock choice to lift the lock.
    template LockedCoin
      with
        coin: Coin
        maturity: Time
        locker: Party
      where
        signatory coin.issuer, coin.owner
    
        controller locker can
          Unlock
            : ContractId Coin
            do create coin
    
../../../_images/lockingByArchiving1.png

Locking By Consuming Choice Diagram

Archiving contract

In the event that changing the original contract is not desirable and assuming the original contract already has an Archive choice, you can introduce another contract, CoinCommitment, to archive Coin and create LockedCoin.

  • Examine the controller party and archiving logic in the Archives choice on the Coin contract. A coin can only be archived by the issuer under the condition that the issuer is the owner of the coin. This ensures the issuer cannot archive any coin at will.
    controller issuer can
      Archives
        : ()
        do assert (issuer == owner)
  • Since we need to call the Archives choice from CoinCommitment, its signatory has to be Issuer.
template CoinCommitment
  with
    owner: Party
    issuer: Party
    amount: Decimal
   where
    signatory issuer
  • The controller party and parameters on the Lock choice are the same as described in locking by consuming choice. The additional logic required is to transfer the asset to the issuer, and then explicitly call the Archive choice on the Coin contract.
  • Once a Coin is archived, the Lock choice creates a LockedCoin that represents Coin in locked state.
    controller owner can
      nonconsuming LockCoin
        : ContractId LockedCoin
        with coinCid: ContractId Coin
             maturity: Time
             locker: Party
        do   
          inputCoin <- fetch coinCid
          assert (inputCoin.owner == owner && inputCoin.issuer == issuer && inputCoin.amount == amount )
          --the original coin firstly transferred to issuer and then archivaed
          prop <- exercise coinCid Transfer with newOwner = issuer
          do          
            id <- exercise prop AcceptTransfer
            exercise id Archives
          --create a lockedCoin to represent the coin in locked state 
          create LockedCoin with 
            coin=inputCoin with owner; issuer; amount
            maturity; locker
../../../_images/lockingByArchiving2.png

Locking By Archiving Contract Diagram

Trade-offs

This pattern achieves locking in a fairly straightforward way. However, there are some tradeoffs.

  • Locking by archiving disables all choices on the original contract. Usually for consuming choices this is exactly what is required. But if a party needs to selectively lock only some choices, remaining active choices need to be replicated on the LockedCoin contract, which can lead to code duplication.
  • The choices on the original contract need to be altered for the lock choice to be added. If this contract is shared across multiple participants, it will require agreement from all involved.