Permissões
Quem pode fazer o quê é a pergunta mais importante de qualquer contrato. A versão 2 da linguagem deixa a resposta explícita no código e visível na interface.
Papéis
role manager
role shipper
init():
grant manager to caller
action hire_shipper(who: address) only manager:
grant shipper to who
action fire_shipper(who: address) only manager:
revoke shipper from who
action ship(id: int) only shipper:
orders[id].status = Status.Shipped
role nomedeclara um conjunto de endereços. Um contrato pode declarar vários papéis; um módulo pode declarar os seus.grant nome to <endereço>adiciona um endereço;revoke nome from <endereço>remove. As duas são instruções e emitemRoleGranted(role, account, by)ouRoleRevoked(role, account, by), então o histórico de permissões fica nos recibos.nome.has(endereço)diz se o endereço tem o papel.- Um papel não é um valor: não pode ser atribuído, passado nem retornado.
- Ninguém tem um papel até o contrato concedê-lo. Conceda o primeiro administrador no
init().
only
state owner: address
state treasurer: address
role auditor
action withdraw(to: address, amount: int) only owner, treasurer:
send(to, amount)
fn audit_log(note: text) only auditor:
emit Audited(caller, note)
only a, b, …vai no fim do cabeçalho da função. A chamada continua secallertiver algum dos papéis listados ou for igual a alguma variável de estado do tipo address listada. Senão falha comrequirement failed: only owner or treasurer can call 'withdraw'e nada muda.- Funciona em actions, auxiliares
fneupgrade(). Não é permitido em views (consultas gratuitas não têm quem chama verificado) nem noinit()(nenhum papel existe ainda). - A verificação roda antes da primeira instrução da função, então nenhum trabalho é feito para quem não tem permissão.
tccl check,tccl abie o playground mostram as regras ao lado de cada função, por exemploaction withdraw(to: address, amount: int) only owner, treasurer.
only owner é exatamente require caller == owner, "only owner can call '…'" escrito de um jeito que as ferramentas conseguem ler. Contratos da versão 1 usam a forma com require.
Padrões
Transferência de dono
state owner: address
state pending_owner: address
action offer_ownership(to: address) only owner:
pending_owner = to
action accept_ownership() only pending_owner:
owner = pending_owner
pending_owner = zero_address()
As duas etapas evitam entregar o contrato a um endereço digitado errado.
Nunca se tranque para fora. Revogar o último administrador é permanente. Mantenha uma contagem se isso importar:
role admin
state admins: int
action add_admin(who: address) only admin:
require not admin.has(who), "already an admin"
grant admin to who
admins += 1
action remove_admin(who: address) only admin:
require admin.has(who), "not an admin"
require admins > 1, "keep at least one admin"
revoke admin from who
admins -= 1
Papéis para contratos. Um papel pode ser concedido a um endereço de contrato. Numa chamada vinda desse contrato, caller é o contrato, então only operator funciona para contratos também — por exemplo um jogo autorizado a criar itens.
Várias aprovações. Para decisões que exigem M de N pessoas, guarde as aprovações por proposta e execute quando a contagem chegar a M — veja treasury.tccl.
Erros a evitar
- Autorizar com
origin. Use semprecaller(ouonly).originé quem assinou a transação mesmo com um contrato desconhecido no meio. - Inicialização desprotegida. Defina donos e conceda papéis no
init(), que roda uma vez na publicação. Uma actionsetupque qualquer um pode chamar primeiro é uma tomada de controle. - Esquecer que views são públicas.
onlynão esconde dados: tudo o que fica gravado na blockchain pode ser lido por qualquer pessoa. - Confiar cegamente na autoridade de upgrade. A autoridade de um contrato atualizável pode trocar o código, inclusive as verificações de permissão. Veja Upgrades.