lorentz-0.13.0: EDSL for the Michelson Language
Safe HaskellNone
LanguageHaskell2010

Lorentz.Tickets

Description

Extended support for the tickets feature.

This module has to be imported explicitly, it is not re-exported by Lorentz.

The primary use case for tickets: authorization tokens for specific actions. For instance, in Pause entrypoint a contract may accept a ticket, checking that it is emitted from the administrator address. The mechanism of tickets is more flexible than sender and source instructions:

  • a ticket can be carried through an arbitrary chain of intermediate agents;
  • it can permit a limited number of certain actions;
  • tickets may have a restricted set of authorized actions.

More details below.

The amount of tokens in a ticket may stand for a number of allowed actions invocations, in this case accepted tickets are checked to contain just 1 token. This semantics makes sense, taking SPLIT_TICKET and JOIN_TICKETS instructions into account, and also the fact that tickets are not dupable.

One important precaution to be kept in mind is that an emitted ticket must permit exactly one kind of action in exactly one contract. This way the ticket emitter can be sure that his ticket cannot be used in an unexpected way. We see two main approaches to this:

  • Make the data carried by the ticket identify the target contract and the action that is being permitted. The contract has to verify the data in the ticket.
  • Make tickets carry as little data as possible, and instead let the main authorized entity keep a single tickets emitting contract per target contract per action (the latter is optional). This way, the burden of separating tickets' area of applicability lies on that main authorized entity instead of the target contract, but actions become cheaper.

Some examples of use cases:

  • An administrator may emit a ticket that permits some delegate to pause the contract. In this case, the ticket should carry a description of the pausing capability (string or enum value) and permission's expiry time.

It is also easy to share admin privileges among other participants by providing them with a ticket with a very large tokens amount. The target contract does not even need to know about many administrators being involved.

  • A user may wish to delegate some actions to an intermediate service. In this case, tickets can serve as signatures made on behalf of the user's contract, with replay protection provided out-of-the-box.

Note that at the moment of writing, implicit addresses cannot serve as ticket emitters.

  • The allowances mechanism known by FA1.2/FA2 may be replaced with tickets. In this case, the tokens amount in a ticket will correspond to the amount of tokens that can be spent by an operator.

This module provides helpers to cover the mentioned cases.

Synopsis

Actions authorization

authorizeAction :: IsoValue td => ((td ': s) :-> s') -> (TAddress emitterParam ': (Ticket td ': s)) :-> s' Source #

Verifies the given ticket value that permits running an action.

  1. Ticketer is checked to match the given address.
  2. Tokens amount in the ticket is checked to be equal to 1.
  3. Ticket data is checked with the provided handler. In case the data contains target contract, consider using verifyTargetContract or verifyTargetContractAnd.

Ticket data verification

verifyTargetContract :: (TAddress selfParam ': s) :-> s Source #

Check data in a ticket when it solely carries target contract address.

verifyTargetContractAnd :: ((d ': s) :-> s') -> ((TAddress selfParam, d) ': s) :-> s' Source #

Check data in a ticket when it carries target contract address and some other data that is to be verified with the given handler.

Tokens spending authorization

newtype STicket (action :: k) td Source #

A ticket kept in storage.

The common pattern of use here is 1. A ticket that permits spending few tokens comes in, it is partially validated and this STicket type is produced. 2. This type can repeatedly be used to consume up to the amount of permitted tokens.

So in order to count allowed tokens, in storage you usually put this type, not bare Ticket.

The action type parameter serves to distinguish tickets for different actions at type level and usually just matches the value in ticket data:

  • If the data in ticket is validated to be "pause", then the result of validation can be STicket "pause" ...; This is the preferred option.
  • If the data in ticket has enum type, and the value is validated to be some PauseAction, then validation can produce STicket PauseAction ....

CAUTION: when working with this type, you must ensure that the expected ticket emitter (the one you validate tickets against, e.g. admin address) is constant for the lifetime of the STicket value. Otherwise, tickets arithmetics (provided via methods below) won't work. So do one of

  • Either never change the expected ticket emitter;
  • Or, when it is changed, wipe all the STicket values validated against the old ticketer from storage;
  • Or keep ticket values in a map where key is the currently expected ticket emitter.

Note some specificity of this type - its values mostly always appear optionally, either in a map, or in Maybe (this is clear from the potential use cases).

Constructors

STicket (Ticket td) 

Instances

Instances details
(CanCastTo a a2, CanCastTo td td2) => CanCastTo (STicket a td :: Type) (STicket a2 td2 :: Type) Source # 
Instance details

Defined in Lorentz.Tickets

Methods

castDummy :: Proxy (STicket a td) -> Proxy (STicket a2 td2) -> () Source #

Generic (STicket action td) Source # 
Instance details

Defined in Lorentz.Tickets

Associated Types

type Rep (STicket action td) :: Type -> Type #

Methods

from :: STicket action td -> Rep (STicket action td) x #

to :: Rep (STicket action td) x -> STicket action td #

(Typeable k, Typeable action, TypeHasDoc td) => TypeHasDoc (STicket action td) Source # 
Instance details

Defined in Lorentz.Tickets

Associated Types

type TypeDocFieldDescriptions (STicket action td) :: FieldDescriptions #

Methods

typeDocName :: Proxy (STicket action td) -> Text #

typeDocMdDescription :: Markdown #

typeDocMdReference :: Proxy (STicket action td) -> WithinParens -> Markdown #

typeDocDependencies :: Proxy (STicket action td) -> [SomeDocDefinitionItem] #

typeDocHaskellRep :: TypeDocHaskellRep (STicket action td) #

typeDocMichelsonRep :: TypeDocMichelsonRep (STicket action td) #

NiceComparable td => IsoValue (STicket action td) Source # 
Instance details

Defined in Lorentz.Tickets

Associated Types

type ToT (STicket action td) :: T #

Methods

toVal :: STicket action td -> Value (ToT (STicket action td)) #

fromVal :: Value (ToT (STicket action td)) -> STicket action td #

(HasAnnotation td, NiceComparable td) => HasAnnotation (STicket action td) Source # 
Instance details

Defined in Lorentz.Tickets

Unwrappable (STicket action td) Source # 
Instance details

Defined in Lorentz.Tickets

Associated Types

type Unwrappabled (STicket action td) Source #

(NiceComparable d, Typeable k, Typeable action) => NonZero (STicket action d) Source # 
Instance details

Defined in Lorentz.Tickets

Methods

nonZero :: forall (s :: [Type]). (STicket action d ': s) :-> (Maybe (STicket action d) ': s) Source #

type Rep (STicket action td) Source # 
Instance details

Defined in Lorentz.Tickets

type Rep (STicket action td) = D1 ('MetaData "STicket" "Lorentz.Tickets" "lorentz-0.13.0-inplace" 'True) (C1 ('MetaCons "STicket" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 (Ticket td))))
type TypeDocFieldDescriptions (STicket action td) Source # 
Instance details

Defined in Lorentz.Tickets

type TypeDocFieldDescriptions (STicket action td) = '[] :: [(Symbol, (Maybe Symbol, [(Symbol, Symbol)]))]
type ToT (STicket action td) Source # 
Instance details

Defined in Lorentz.Tickets

type ToT (STicket action td) = ToT (Ticket td)
type Unwrappabled (STicket action td) Source # 
Instance details

Defined in Lorentz.Tickets

type Unwrappabled (STicket action td)

toSTicket :: IsoValue td => (Ticket td ': s) :-> (Address ': (td ': (STicket action td ': s))) Source #

Constructs an STicket.

This also emits the ticketer address and ticket data for further use. You are oblidged to do something with both of them:

This way you can be sure that arithmetics on STickets works smoothly and there are no, for instance, attempts to join two tickets from different emitters.

coerceUnwrap :: forall a s. Unwrappable a => (a ': s) :-> (Unwrappabled a ': s) Source #

Specialized version of forcedCoerce_ to unwrap a haskell newtype.

Permitted tokens arithmetics

readSTicket :: (STicket action td ': s) :-> (ReadTicket td ': (STicket action td ': s)) Source #

Read contents of STicket.

sTicketAmount :: IsoValue td => (Maybe (STicket action td) ': s) :-> (Natural ': s) Source #

Read tokens amount in STicket.

subtractSTicket :: (IsoValue d, KnownValue (STicket action d)) => (forall s0. ErrInstr (("permitted" :! Natural, "spent" :! Natural) ': s0)) -> (Natural ': (Maybe (STicket action d) ': s)) :-> (Maybe (STicket action d) ': s) Source #

Consume given amount of tokens from the ticket, failing with given handler if amount of tokens in the ticket is insufficient.

We do not provide a ready error for this case since it will probably depend on particular tokens that the given STicket serves to permit.

Note that you may want to run isSome nonZero none on the result to save storage space.

subtractSTicketPlain :: IsoValue d => (forall s0. ErrInstr (("permitted" :! Natural, "spent" :! Natural) ': s0)) -> (Natural ': (STicket action d ': s)) :-> (STicket action d ': s) Source #

Similar to subtractSTicket, but without Maybe.

You may want to run nonZero after this function call to save storage space.

addSTicket :: (STicket action d ': (Maybe (STicket action d) ': s)) :-> (Maybe (STicket action d) ': s) Source #

Adds tokens permitted by an incoming ticket.

Useful when someone permits you to spend even more tokens than you already have been allowed to spend.

This assumes that the ticket being added has already been verified. The passed tickets must be verified against the same ticket data and ticket emitter.

addSTicketPlain :: (STicket action d ': (STicket action d ': s)) :-> (STicket action d ': s) Source #

Similar to addSTicket, but without Maybe.

Generic tickets verification

verifyTicketGeneric Source #

Arguments

:: IsoValue td 
=> (forall s0. (Natural ': s0) :-> s0)

Ticket tokens verifier

-> ((td ': s) :-> s')

Ticket data verifier

-> (TAddress emitterParam ': (Ticket td ': s)) :-> (Ticket td ': s') 

Generic method for verifying tickets that authorize some action.

For concrete example of use see authorizeAction.

verifyTicketGeneric' Source #

Arguments

:: IsoValue td 
=> (forall s0. (Address ': (Address ': s0)) :-> s0)

Ticket emitter verifier

-> (forall s0. (Natural ': s0) :-> s0)

Ticket tokens verifier

-> ((td ': s) :-> s')

Ticket data verifier

-> (TAddress emitterParam ': (Ticket td ': s)) :-> (Ticket td ': s') 

Verifies a ticket.

This is an extremely generified verifyTicketGeneric, allows providing verifiers for all the ticket's parts. Provided just in case.

verifyTicketer :: (Address ': (Address ': s)) :-> s Source #

Generic method for verifying the ticketer.

Orphan instances