Skip to main content

Permits

Implements TZIP-17 norm for fee-less operations.

Repository


Author


Norm


Templates

Errors

constant EXPIRY_TOO_BIG        : string = "EXPIRY_TOO_BIG"
constant USER_PERMIT_NOT_FOUND : string = "USER_PERMIT_NOT_FOUND"
constant PERMIT_NOT_FOUND : string = "PERMIT_NOT_FOUND"
constant MISSIGNED : string = "MISSIGNED"
constant PERMIT_EXPIRED : string = "PERMIT_EXPIRED"

constant string

Storage

consumer

Code


asset consumer {
cid : address
}

asset address

Type


Related

permits

Code


record user_permit {
expiry : option<nat>;
created_at : date;
}

asset permits to big_map {
user_address : address;
counter : nat = 0;
user_expiry : option<nat> = none;
user_permits : map<bytes, user_permit> = [];
}

record option nat date asset address map bytes

Type


Related

default_expiry

Default permits expiration duration set to one year (expressed in seconds).

Code


variable default_expiry : nat = 31556952

variable duration

Type


Related

Functions

get_default_expiry(addr)

Code


function get_default_expiry(addr : address) : nat {
return (permits[addr] ? (the.user_expiry ? the : default_expiry) : default_expiry)
}

function address nat ?:

Parameter


addr :

Address to get expiry of

Returns


addr's expiry or default expiry if not found

Fails with

does not fail


Related

get_expiry(addr, permitkey)

Code


function get_expiry(addr : address, permit_key : bytes) : nat {
return (permits[addr] ? let d = (the.user_expiry ? the : default_expiry) in
(the.user_permits[permit_key] ?
(the.expiry ? the : default_expiry) : d) :
default_expiry
)
}

function address bytes nat [] ?: let []

Parameters


addr :

Address to get expiry of


permit_key :

Permit key

Returns


addr's permit permit_key expiry or default expiry if not found

Fails with

does not fail


Related

has_expired(userp, expiry)

Code


function has_expired(up : user_permit, e : nat) : bool {
return (up.created_at + (up.expiry ? the : e) * 1s < now)
}

function bool + ?: * < now

Parameters


User permit


e :

Expiry

Returns

Fails with

does not fail


Related

Entrypoints

manage_consumer(op)

Code


enum consumer_op =
| add<address>
| remove<address>

entry manage_consumer(op : consumer_op) {
called by owner
effect {
match op with
| add(a) -> consumer.add({ a })
| remove(a) -> consumer.remove(a)
end
}
}

enum address entry called by effect match add remove

Parameter


Consumer operation specification:
  • add(a) to add consumer a
  • remove(a) to remove consumer a

Fails with

does not fail


Related

set_expiry(v, p)

Code


entry set_expiry(iv : option<nat>, ip : option<bytes>) {
require {
p1: is_not_paused();
p2: iv ? the < default_expiry : true otherwise EXPIRY_TOO_BIG;
}
effect {
const caller_permit ?= permits[caller] : (USER_PERMIT_NOT_FOUND, caller);
match ip with
| some(p) -> begin
if (iv ? the > 0 : true) then begin
const up : user_permit ?=
caller_permit.user_permits[p] : (PERMIT_NOT_FOUND, (caller, p));
permits[caller].user_permits.update(p, some({ up with expiry = iv }))
end else begin
permits[caller].user_permits.remove(p)
end
end
| none -> permits.update(caller, { user_expiry = iv })
end
}
}

entry require effect const ?= [] match begin if ?: [] update remove

Parameters


User permit


Expiry

Fails with

"CONTRACT_PAUSED"

When contract is paused.


"EXPIRY_TOO_BIG"

When iv is some value greater the default_expiry.


("USER_PERMIT_NOT_FOUND", caller)

When caller is not found in permits.


("PERMIT_NOT_FOUND", (caller, p))

When ip is some value of a permit key not found in caller's permits.


Related

set_default_expiry(v)

Code


entry set_default_expiry(v : nat) {
called by owner
require { p3: is_not_paused() }
effect {
default_expiry := v
}
}

entry called by require effect :=

Parameter


v :

New default expiry value

Fails with

"INVALID_CALLER"

When caller is not owner


"CONTRACT_PAUSED"

When contract is paused.


Related

permit(pk, sig, data)

Code


entry permit(pk : key, sig : signature, data : bytes) {
constant {
user is key_to_address(pk);
usr_permit is permits[user] ?
(the.counter, the.user_permits) :
(0, make_map<bytes, user_permit>([]));
pcounter is usr_permit[0];
puser_permits is usr_permit[1];
to_sign is pack(((self_address, self_chain_id), (pcounter, data)));
usr_expiry is get_default_expiry(user);
}
require {
p4: is_not_paused();
p5: check_signature(pk, sig, to_sign) otherwise (MISSIGNED, to_sign)
}
effect {
permits.add_update(user, {
counter += 1;
user_permits = put(puser_permits, data, {
expiry = some(usr_expiry);
created_at = now
})
});
for (k, v) in permits[user].user_permits do
if has_expired(v, usr_expiry)
then permits[user].user_permits.remove(k)
done
}
}

entry key signature bytes constant key_to_address [] ?: make_map [] pack self_address self_chain_id require check_signature effect add_update put some now for if remove

Parameters


pk :

Public key that signed data


sig :

Signed data by pk.


data :

Permit data.

Fails with

"CONTRACT_PAUSED"

When contract is paused.


("MISSIGNED", to_sign)

When sign is not obtained from data.


Related

consume(from, data, err)

Code


entry consume(signer : address, data: bytes, err: string) {
called by consumer
constant {
permit_key is blake2b(data);
signer_expiry is get_expiry(signer, permit_key);
lpermit ?is permits[signer] otherwise USER_PERMIT_NOT_FOUND;
luser_permits ?is lpermit.user_permits[permit_key] otherwise err;
}
require {
p6: is_not_paused()
}
fail if {
p7 : has_expired(luser_permits, signer_expiry) with PERMIT_EXPIRED
}
effect {
permits[signer].user_permits.remove(permit_key)
}
}

entry address bytes string called by constant blake2b [] fail if remove

Parameters


%from :

Public key that signed data


sig :

Signed data by pk.


data :

Permit data.

Fails with

"INVALID_CALLER"

When caller is not a consumer.


"CONTRACT_PAUSED"

When contract is paused.


"USER_PERMIT_NOT_FOUND"

When signer is not found in permits.


"PERMIT_EXPIRED"

When signer's permit has expired.


Related

check(signer, sig, data)

Code


entry check(signer : key, sig : signature, data : bytes) {
called by consumer
constant {
pkh is key_to_address(signer);
lcounter is permits[pkh] ? the.counter : 0;
to_sign is pack((self_address, lcounter, blake2b(data)));
}
require {
p8: is_not_paused();
p9: check_signature(signer, sig, to_sign) otherwise (MISSIGNED, to_sign)
}
effect {
permits.add_update(pkh, { counter = (lcounter + 1)});
}
}

entry key signature bytes called by constant key_to_address [] ?: pack self_address blake2b require check_signature effect add_update

Parameters


%from :

Public key that signed data


sig :

Signed data by pk.


data :

Permit data.

Fails with

"INVALID_CALLER"

When caller is not a consumer


"CONTRACT_PAUSED"

When contract is paused.


("MISSIGNED", to_sign)

When sign is not obtained from data.


Related