Desafios com contratos inteligentes orientados a objetos

A batalha entre a capacidade de atualização e a confiança

Stephen Fiser Blocked Unblock Seguir Seguindo 11 de janeiro

Trabalhar com contratos inteligentes em uma plataforma como a Ethereum é um desafio interessante se você passou muito tempo trabalhando em um ambiente de desenvolvimento de software mais convencional. Uma vez implantado, um contrato inteligente nunca pode ser removido da rede e, portanto, o comportamento padrão é que ele tenha vida longa.

No início, coisas como kill switches emergiram para essencialmente fechar um contrato no caso de algo dar errado. Esses interruptores kill não podiam ser ativados por qualquer um, mas normalmente precisavam ser ativados pelo criador do contrato ou criadores (isto é, funções de controle centralizadas).

Esses interruptores kill simples destacam uma troca extremamente importante na computação descentralizada: capacidade de atualização versus confiança .

Capacidade de atualização

A capacidade de atualização pode ser definida como a capacidade de os programadores modificarem o comportamento do software em algum momento no futuro.

Os interruptores Kill se encaixam claramente nesta definição, mesmo que em um sentido limitado. Os interruptores Kill normalmente precisam ser pré-programados no contrato e, portanto, seriam transparentes para um usuário astuto (mas provavelmente não um usuário médio). Uma classe mais ampla de capacidade de atualização incluiria a capacidade de liberar código adicional ou modificado que representa alterações ou aprimoramentos no sistema.

A capacidade de atualizar contratos inteligentes é desejável no sentido mais amplo por vários motivos:

  • Corrigindo defeitos no código quando eles são encontrados
  • Atualizando a base de código quando novos padrões emergem ou metodologias mais eficientes são encontradas
  • Alterar o comportamento do código conforme os requisitos evoluem

É importante observar que esses aspectos são desejáveis durante o desenvolvimento e após a implantação. Um sistema completamente inflexível torna-se difícil de desenvolver quando atinge um determinado tamanho. É comum ver contratos inteligentes com centenas e centenas de linhas. Eles normalmente contêm seu próprio armazenamento de dados, que é manipulado pelas regras de negócios definidas no mesmo contrato.

Algo que começamos a experimentar com um tempo atrás é dividir o código que representa as regras de negócios em contratos separados do código que simplesmente gerencia os dados. Para ser eficaz, as regras de negócios precisam ter total capacidade de modificar o estado dos dados. Como grande parte da segurança de um ambiente de programação Blockchain como o Solidity gira em torno de restringir o comportamento baseado no msg.sender , separar a lógica do contrato onde os dados estão sendo manipulados significa que você precisa de contratos protegidos – contratos que não têm exposição ao mundo exterior. Esses contratos só podem ser chamados por outros contratos ou endereços que tenham sido colocados na lista de permissões.

Dividir os dados e o comportamento em contratos separados introduz várias propriedades interessantes.

Primeiro de tudo, a migração de dados no Blockchain é problemática por vários motivos. Encapsular o armazenamento de dados com a funcionalidade simples do CRUD em seu próprio contrato diminui a probabilidade de que o contrato que o contém precise mudar. Ainda não é zero. Pense no número de vezes que as tabelas do banco de dados são alteradas. No entanto, mover as regras de negócios para seus próprios contratos separados nos permite atualizar a lógica do sistema sem precisar recriar o estado dos dados.

Além disso, dividir o armazenamento de dados e a lógica de negócios nos permite definir interfaces estáveis para tópicos específicos, mantendo a flexibilidade de implementação.

Isso é ótimo por algumas razões.

Primeiro, é uma boa prática de software não depender de coisas que você não usa. Portanto, se um sistema externo depende de um conjunto específico de funcionalidades do nosso sistema, é útil que elas dependam desse subconjunto de funcionalidades e não de todo o sistema.

Por exemplo, suponha que tenhamos um sistema de contrato inteligente para aluguel de carros. Decidimos colocar todo o código no One Big Contract ™ . Nosso contrato contém uma lógica sobre várias coisas, mas um terceiro chamado CarRep está preocupado apenas em olhar para o histórico de aluguel de carros das pessoas para um novo sistema de reputação no qual estão trabalhando.

Lo and Behold, precisamos atualizar como as limpezas de carros são registradas em nosso contrato e, assim, corrigimos o problema e implantamos um novo contrato. É claro que corrigimos todos os nossos aplicativos internos para apontar para o novo contrato e notificamos as pessoas que (nós sabemos) estão usando nosso contrato da mudança. Agora CarRep tem que mudar seu código porque nós decidimos mudar como lidamos com limpezas de carro. No entanto, existe uma possibilidade diferente de zero que nem sequer sabemos sobre o CarRep e, se não nos comunicarmos bem, acabamos de quebrar o sistema deles.

Se dividíssemos nossa lógica em contratos diferentes, o CarRep não precisaria mudar nada.

Segundo, se definirmos interfaces de alto nível que apontam para implementações da lógica de negócios, o CarRep precisaria apenas alterar seu código no caso de alterarmos a API pública de alto nível (contrato Proxy). Se formos cuidadosos com nossas definições e nomes, essas mudanças podem ser raras. Isso é essencialmente o mesmo de como esperamos que grandes versões de APIs públicas, como o Stripe ou o Twitter, funcionem.

Finalmente, seguindo o padrão que temos estabelecido, nosso sistema pode seguir o Princípio Open-Closed – podemos adicionar um novo comportamento ao sistema sem precisar modificar o código-fonte. Podemos simplesmente implantar novos componentes e colocá-los na lista de permissões quando necessário, para que eles tenham autorização para interagir com nosso sistema.

Confiar em

Uma das primeiras idéias promovidas em relação aos contratos inteligentes é o fato de que eles são vinculativos – daí a palavra “contrato”. As descrições foram algo como: “Joe e eu fazemos uma aposta sobre quem vencerá uma eleição. Uma vez feito o acordo, não pode ser desfeito. Uma vez terminada a eleição, o contrato inteligente recompensará o vencedor ”. Nesse sentido, os contratos inteligentes também são“ sem confiança ”, porque eu posso depender completamente do código para executar conforme especificado sem intermediação.

Isto está em contraste com os argumentos anteriores sobre a necessidade de atualizar contratos. Se eu puder, sozinho, atualizar um contrato, então posso fazer todo tipo de coisas maliciosas ou tendenciosas, e reintroduzi a necessidade de as pessoas confiarem em mim.

Isso apresenta um conflito fundamental. Como Robert Martin aponta em seu livro Clean Architecture ,

“O software deve ser suave – isto é, deve ser fácil mudar”.

Se eu não posso mudar o código, todos os tipos de problemas podem surgir. Por outro lado, se um contrato pode mudar, é um contrato?

Parte do problema pode ser que o termo “contrato inteligente” é mal escolhido . Algumas coisas certamente cairão na categoria de contratos, mas um ambiente de programação de propósito geral como o Solidity on Ethereum permite que todos os tipos de coisas sejam criados – alguns dos quais serão menos contratuais por natureza.

Na minha opinião, este é um problema que está longe de ser resolvido. No entanto, vou oferecer alguns pensamentos:

Primeiro, em escala, eu esperaria que um número muito pequeno de usuários realmente lesse código e entendesse o que eles estão assinando. Então, nesse sentido, muitas vezes eles estarão confiando que a empresa, o site, a plataforma, etc. com os quais estão interagindo não são maliciosos.

Segundo, em muitos contextos, esse problema pode ser resolvido exigindo que vários desenvolvedores assinem as alterações que estão sendo implementadas. Isso reduzirá a probabilidade de um desenvolvedor invadir e roubar pessoas.

Em terceiro lugar, para sistemas muito sensíveis, os auditores externos podem ser obrigados a assinar as alterações, além de vários desenvolvedores, antes que as alterações sejam eliminadas. Os TCRs podem ser usados para gerenciar listas de desenvolvedores e auditores aprovados em situações em que a descentralização total é necessária.

Este tópico está profundamente relacionado a vários projetos em que estou trabalhando, e continuarei investigando e escrevendo sobre isso enquanto aprendo coisas novas.