Posibles problemas con los contratos

El lenguaje de Marlowe está diseñado para tener el menor número posible de escollos y gotchas, de modo que los contratos puedan escribirse de forma intuitiva, evitando cualquier sorpresa. No obstante, es imposible por diseño excluir todos los contratos que no deben escribirse, sin que Marlowe sea mucho más difícil de usar. Además, incluso cuando un contrato está bien escrito, es posible que sus usuarios interactúen con él de forma no válida, emitiendo transacciones no válidas.

En todos los casos, cuando se producen estos efectos no deseados, Marlowe está diseñado para comportarse de la forma más intuitiva y conservadora posible. Sin embargo, merece la pena ser consciente de estos problemas potenciales, y revisar cómo se comporta Marlowe en estas situaciones. Ese es el tema de este tutorial.

 

Advertencias

Las advertencias de Marlowe son indicaciones de que un contrato está mal escrito. Un contrato bien escrito nunca debería emitir una advertencia, independientemente de cómo interactúen los usuarios con él. Idealmente, nos gustaría prohibir que se escriban contratos que puedan emitir advertencias, pero eso requeriría que los contratos Marlowe fueran de tipo dependiente, y escribir expresiones de tipo dependiente es mucho más engorroso.

En su lugar, Marlowe permite escribir contratos que emitan advertencias, y proporcionamos herramientas de análisis estático que permiten a los desarrolladores de contratos comprobar si un contrato concreto puede emitir advertencias. Además, proporcionamos comportamientos alternativos para cuando un contrato produce una advertencia, a pesar de nuestro consejo. Proporcionamos comportamientos alternativos porque reconocemos que el análisis de los contratos grandes puede ser muy caro desde el punto de vista computacional, y porque se pueden cometer errores. Queremos que los contratos mal escritos fallen de la forma más inofensiva posible, es decir, de forma conservadora.

 

Pagos no positivos

Cuando un contrato deba pagar una cantidad de dinero inferior a una unidad de una moneda o token, emitirá una advertencia NonPositivePay , y no transferirá ningún dinero.

Los pagos negativos deben ser implementados como depósitos positivos (cuando se paga a un participante), o pagos positivos en la dirección opuesta (cuando se paga entre cuentas).

 

Depósitos no positivos

Cuando se supone que un contrato espera una cantidad de dinero inferior a una unidad de una moneda de token, igual esperará una transacción IDeposit , pero esa transacción no necesita transferir ningún dinero al contrato y no se transfiere ningún dinero al participante que emite la transacción. Una vez que este depósito "falso" tiene éxito, el contrato emitirá una advertencia NonPositiveDeposit .

Los depósitos negativos deben realizarse siempre como pagos positivos.

 

Pago parcial

Cuando un contrato debe pagar una cantidad de dinero superior a la que hay en la cuenta de origen, se limitará a transferir lo que haya en esa cuenta, aunque haya dinero suficiente en todas las cuentas del contrato, y emitirá una advertencia PartialPay .

Los pagos parciales deben evitarse porque un contrato que nunca produce un pago parcial es un contrato explícito. Los contratos explícitos garantizan a sus usuarios que serán ejecutables y que en cualquier parte del contrato que diga que se va a producir un pago, efectivamente se producirá.

 

Let shadowing (sombreado) (no cubierto por el análisis estático)

Cuando un contrato llega a una construcción Let que redefine un valor con un identificador que ya estaba definido por un Let externo, el contrato emitirá una advertencia Shadowing y reemplazará la definición anterior.

El shadowing es una mala práctica de programación porque lleva a la confusión. Utilizar el mismo identificador para más de una cosa puede inducir a los desarrolladores o usuarios a pensar que un uso de Use va a ser evaluado a una cantidad mientras que en realidad va a ser evaluado a otra cantidad diferente.

 

Malos olores

Hay otros "malos olores" que indican que un contrato ha sido probablemente mal diseñado.

Estos contratos son válidos, en el sentido de que no causarán necesariamente ninguna advertencia, y hacen lo que dicen que hacen, pero tienen características que sugieren que, o bien el programador del contrato no era plenamente consciente de las consecuencias del mismo, o bien que el programador redactó el contrato a propósito de forma confusa para el lector.

 

Uso de Let indefinido (debería ser una advertencia)

Cuando una construcción Use utiliza un identificador que aún no ha sido definido, se evaluará con el valor por defecto de 0. No se emitirá ninguna advertencia pero, de nuevo, es una mala práctica porque puede ser engañosa. (Constant 0) debería utilizarse en su lugar, ya que hace explícita la cantidad en cuestión.

 

Partes inalcanzables de un contrato

Este es el principal mal olor de los contratos de Marlowe. Si una parte del contrato es inalcanzable, ¿por qué se ha incluido en primer lugar?

Este mal olor adopta varias formas.

 

Sub-Contract es inalcanzable

Por ejemplo:

El contrato anterior equivale a contract2. En general, nunca se debe usar FalseObs, y sólo deberías usar TrueObs como la observación raíz de una construcción Case .

 

Observation siempre es abreviada

Por ejemplo:

La observación anterior equivale a observation1. Nuevamente, sólo deberías usar TrueObs como la observación raíz de una construcción Case .

 

Una rama When no es accesible

Por ejemplo:

contract2 es inaccesible, todo el Case podría eliminarse del contrato y el comportamiento sería el mismo.

 

Timeouts anidados no incrementados

Por ejemplo:

contract1 es inaccesible: después del slot 10, el contrato evolucionará directamente a contract2. El When interno no hace ninguna diferencia en el contrato.

 

Problemas de usabilidad

Incluso si un contrato evita las advertencias y no tiene código inalcanzable, podría permitir a los usuarios malintencionados forzar a otros usuarios a situaciones indeseables que no estaban previstas originalmente por el desarrollador del contrato.

 

Malas cadencias de construcciones When 

Considera el siguiente contrato:

En principio no hay nada malo en este contrato, pero si (Role "alice") hace su elección en el slot 9, será prácticamente imposible que bob haga su elección a tiempo y obtener la devolución del dinero en su cuenta (Role "bob"). A no ser que esto forme parte de un juego y sea un efecto buscado, es probable que sea un contrato injusto para (Role "bob").

En general, una buena práctica es asegurarse que las contrucciones When tengan timeouts crecientes, y que el aumento entre timeouts es razonable para que las diferentes partes emitan y consigan que sus transacciones sean aceptadas por la blockchain. Hay muchas razones por las que la participación de una parte puede retrasarse: un fallo en el suministro de energía, un pico repentino en el número de transacciones pendientes en la blockchain, ataques a la red, etc. Así que es importante dejar tiempo suficiente, y ser generoso con los timeouts y con los aumentos de los mismos.

 

Errores

Por último, aunque un contrato esté perfectamente redactado, los usuarios pueden utilizarlo de forma incorrecta, y a esos usos incorrectos los llamamos errores de uso.

En todos los casos, siempre que una transacción provoque un error, la transacción no tendrá ningún efecto sobre el Contract o en su State. De hecho, el wallet de un usuario sabrá de antemano si una transacción va a producir un error, porque las transacciones son deterministas, por lo que los usuarios nunca deberían necesitar enviar una transacción errónea a la blockchain.

 

Intervalo ambiguo

Cuando una transacción alcanza un timeout, su intervalo de slot debe ser inequívoco sobre si el timeout ha pasado o no. Por ejemplo, si el mas alto When de un contrato tiene timeout 10 y una transacción con intervalo de slot [6, 14] se emite, la transacción causará un error AmbiguousSlotIntervalError , porque es imposible saber si el timeout ha pasado sólo mirando a la transacción. Para evitar esto, la transacción debe dividirse en dos transacciones separadas:

  1. Una con intervalo de slot [6, 9].
  2. Otra con intervalo de slot [10, 14].

 

Aplicar no-match

Si una transacción no proporciona las entradas que espera el Contract, entonces el contrato emitirá un error NoMatchError , y se descartará toda la transacción.

 

Transacción inútil

Si una transacción no tiene ningún efecto sobre el Contract o State, resultará en un error UselessTransaction , y la transacción completa será descartada. La razón por la que descartamos las transacciones inútiles es que abren la puerta a los ataques de denegación de servicio (DoS), porque un atacante potencial podría inundar el contrato con transacciones innecesarias e impedir que las transacciones necesarias lleguen a la blockchain.

© Copyright 2020, IOHK Revision bca9eb4b.

Encuentra una copia oficial de este documento aquí:

https://alpha.marlowe.iohkdev.io/doc/marlowe/tutorials/potential-problems-with-contracts.html

https://docs.cardano.org/projects/plutus/en/latest/marlowe/tutorials/potential-problems-with-contracts.html

 

Más traducciones de Cardano en: Cardano For The World