Introducción
Hola a todos. Hoy vamos a revisar 2 contratos en lugar de 1. Incluiremos contratos que involucran llamadas de contratos cruzados y hablaremos sobre promesas y cómo funcionan en NEAR. Si deseas obtener más información sobre cómo funciona la composición, esta es una buena sesión para leer. El primer contrato que vamos a ver se llama contrato de lista blanca y se usa en la red principal para incluir grupos de participación en la lista blanca. Esto es importante porque la distribución de tokens se realiza a través de contratos inteligentes. Usamos contratos de bloqueo que eventualmente revisaremos en esta serie, pero la forma en que funciona es que el bloqueo es un contrato independiente que contiene tokens que se supone que se liberarán durante un período de tiempo. Digamos que los tokens se lanzan durante 2 años y se asignan linealmente a cada bloque. Lo que queríamos hacer es dar la posibilidad de hacer staking (Delegarlos en un grupo de Participación) con estos tokens, incluidos los tokens que aún no se han lanzado. Esto significa que debería poder delegar todos los tokens que ha bloqueado durante 2 años, por ejemplo, y comenzar a ganar recompensas con ellos. Esto se hace en un contrato inteligente, y un contrato de bloqueo esencialmente llama al contrato del grupo de participación que revisamos antes y transfiere tokens de un contrato a otro. Los tokens del contrato de bloqueo salen de la cuenta y van a la cuenta del grupo de participación. Si un grupo de participación no brinda las garantías que se requieren, como la capacidad de devolver estos tokens, significa que habrá problemas. Digamos que yo construyo un grupo de participación personalizado que me permite no solo apostar tokens, sino también retirarlos a cualquier cuenta. Este tipo de operación te permitirá obtener activos líquidos antes de que finalice el período de liberación. Entonces podrás retirarte, y este no es el comportamiento deseado.
Es por eso que introdujimos la lista blanca donde las implementaciones personalizadas de los grupos de participación que están aprobadas por la Fundación NEAR pueden usarse mediante contratos de bloqueo. Al mismo tiempo, queríamos dar la posibilidad de crear nuevos grupos de participación que ya estén aprobados por cualquier persona sin pasar por la aprobación de la Fundación NEAR. Esto permite que cualquiera pueda crear un nuevo grupo de participación a través de una fábrica de grupos de participación. La fábrica de grupos de participación es el segundo contrato que revisaremos hoy. La forma en que funciona es cuando un contrato de bloqueo quiere delegar, antes de que puedan transferir fondos a este contrato, primero deben seleccionar un grupo de participación. Cuando selecciona el grupo de participación, el bloqueo emite una transacción para verificar si una identificación de cuenta determinada está en la lista blanca en un contrato de lista blanca, y si devuelve verdadero, lo que significa que la cuenta está en la lista blanca, entonces el contrato de bloqueo puede continuar con la delegación. Permite que el bloqueo se transfiera realmente a este contrato. Significa que el contrato del grupo de participación tiene algunas garantías y API que el contrato local espera, y no bloqueará los tokens del propietario ni robará tokens del contrato de bloqueo. Esto también fue importante para los programas de inversión de los empleados de NEAR. Estaba en un cronograma de inversión de cuatro años, y eso permite que la fundación emita una transacción específica para el bloqueo de esa persona con el fin de retirar todo del fondo de participación y devolver el monto invertido a la fundación NEAR en caso de que un empleado se vaya del trabajo o fuera despedido. Ese es el trasfondo de los contratos de bloqueos y de lista blanca.
El repositorio original de estos contratos se puede encontrar en el Github de NEAR. Aquí está el video original en el que se basa esta guía:
Contrato de lista blanca
Estructura principal
Veamos el contrato de lista blanca. En realidad, es un contrato bastante simple, y realmente no tiene mucha lógica, ya sabemos la mayoría de las cosas.
Utiliza una API de NEAR que se llama LookupSet. Es similar a un conjunto desordenado. Es una colección persistente, pero no tiene iteradores, por lo que no puedes iterar sobre las claves de los elementos del conjunto. Solo puedes verificar si un elemento dado está presente o no, y puedes agregarlo al conjunto. No puedes verificar qué elementos están presentes en este conjunto. Al hacer esto se mejora la eficiencia del almacenamiento y reduce el acceso de múltiples lecturas a unas pocas lecturas. El contrato contiene algunos campos. El primero es foundation_account_id, este es la identificación de la cuenta que controla la lista blanca significa que esta cuenta puede incluir en la lista blanca los grupos de participación por 1, y también puede incluir en la lista blanca las fábricas de grupos de participación. La fábrica es un contrato que puede crear una nueva instancia de grupo de participación. La forma en que funciona es que cuando emite una transacción hacia la fábrica de participación que está incluida en la lista blanca por este contrato crea una nueva cuenta como una subcuenta de la fábrica. En nuestro caso, en la red principal se llama poolv1.near, que es la fábrica de grupos de participación que usamos. Crea un contrato, por ejemplo, bisontrails.poolv1.near, en el que implementa el código del grupo de participación en la lista blanca que esta fábrica puede producir. Entraremos en la fábrica del grupo de participación más tarde, pero al mismo tiempo también puede incluir en la lista blanca este grupo de participación determinado. Así es como funciona. La inicialización del contrato solo toma un argumento foundation_account_id. Una cuenta de fundación tiene permisos más altos en este contrato.
Getters
Hay un montón de getters (Captadores).
Puedes verificar si una determinada entrada de participación está en la lista blanca. Así es como un contrato de bloqueo verifica si el grupo está en la lista blanca. Básicamente, solo verifica si un elemento está presente en un conjunto. La segunda forma en que también puede verificar es si la fábrica está en la lista blanca, pero esto no es realmente necesario y nadie lo llama.
Agregar método de grupo de participación
Este es un método que puede ser llamado tanto por una fábrica como por la fundación. Lo que hacemos aquí es verificar que este método de staking agregue un nuevo staking_pool_account_id a una lista blanca. Verifica si la identificación de la cuenta es válida, entonces, verificamos si se trata de una fábrica. Comprobamos en un conjunto que el llamador de este método está presente en la lista blanca de las fábricas. Si es llamado por la fábrica del grupo de participación, entonces está bien. De lo contrario, tiene que ser una identificación de cuenta de la fundación; de lo contrario, el contrato fallará y este método entrará en pánico. Si pasamos la verificación de permisos, simplemente agregamos este grupo de participación a la lista blanca. En este momento solo tenemos una implementación para el grupo de participación, pero en teoría podemos modificar esta implementación cuando introducimos slashing (Penalización) , por ejemplo, y un grupo de participación debe tener el permiso necesario, necesita mantener un saldo mínimo. Hay algún otro cambio en el que necesitamos modificar un contrato validado durante el período de inversión de todos es de 4 años. Pueden suceder muchas cosas en la red y necesitamos tener la capacidad de cambiar potencialmente los grupos de participación. Si la lógica del grupo de staking cambia, por ejemplo. Permite la creación de una nueva fábrica que sea una mejor versión o admita algo que antes no se admitía. No permite cambiar una tarifa de recompensa al instante, sino que solo permite cambiar después de un período de espera de 7 días, o alguna otra idea para una modificación será un factor diferente.
A continuación, solo la fundación puede llamar a remove_staking_pool, por lo que solo la fundación puede eliminar un grupo de participación.
Además, add_factory solo puede ser llamado por la fundación. Es básico, solo se agrega a una lista blanca de las cuentas de fábrica.
Finalmente, remove_factory solo puede ser llamado por la fundación y elimina el factory_account_id. Digamos que la primera fábrica caduca, luego la fundación básicamente puede eliminar la fábrica de la lista blanca, así como también eliminar todos los grupos anteriores de la lista blanca. Ahora, no podrá seleccionar una de las encuestas de participación anteriores de un contrato de bloqueo, y finalmente hay una verificación de que esto es llamado por la fundación, lo cual es una comparación. Este es un contrato muy simple, y solo opera en conjuntos internos y el único método verdadero que es visible desde el exterior es is_whitelisted. Es bastante simple, es solo un grupo de setters y getters.
Inmutabilidad de los contratos inteligentes
La forma en que generalmente se comportan los contratos inteligentes, lo cual es cierto en Ethereum y en otras plataformas, es que son inmutables en sí mismos. En Ethereum, el interior de cada contrato es inmutable, pero usan un contrato proxy que les permite actualizar el punto final para ciertos contratos que son muy críticos para los tokens. En este caso, nuestros contratos principales son en esencia completamente inmutables, y pensamos en ellos como si los fuéramos a implementar una vez y probablemente no podamos cambiarlos, porque de lo contrario tendrá que hacer una bifurcación dura y convencer a todos los validadores para hacer algún tipo de migración de código. Es importante, porque el control sobre ellos debe hacerse a nivel de contrato en lugar de algunas entidades centralizadas. Por ejemplo, si bien la fundación aún mantiene un gran control sobre los grupos de participación al tener la capacidad de eliminar los grupos de participación aquí, no tiene el control para incluir en la lista negra a una entidad en particular en el mundo real para que no implemente un grupo de participación. Todavía pueden crear un grupo de participación con el mayor anonimato posible y crear un grupo de participación sin pedir permiso para convertirse en un validador en la red principal. Algunas cosas provienen de la descentralización, otras provienen de la limitación del control. Si bien se supone que la base debe respaldar la red, es posible que, en algunos escenarios, la base se vea obligada a hacer algo malo por la red. Digamos que el gobierno entra y trata de forzarlos. Si tienen menos capacidad para hacer esto, entonces hay una mayor seguridad en la red. Cuando diseñamos un contrato, pensamos: “¿Cuál es la cantidad de valor en este contrato?” o “¿Cuál es la capacidad de este contrato para influir en la red en general?”. Si es un valor pequeño, entonces está bien mantener la credibilidad mientras la comunidad confíe, si es un valor grande, entonces no está bien. Ya vamos a llegar al contrato de bloqueo, y por la forma en que está diseñado, puedes ver cómo, por ejemplo, la adquisición de derechos se diseñó para, por un lado, permitir que la fundación retire los fondos restantes, pero al mismo tiempo evitar que la fundación retire los fondos adquiridos. Es una forma legal de hacer las cosas, excepto que está escrito en el código. La lista blanca es un contrato muy crítico, porque antes de que se bloqueen los fondos, la mayoría de los fondos controlan la red a través de contratos locales en grupos de participación a través de esta lista blanca en particular, por lo que era importante diseñarlo de tal manera que mantuviera la descentralización y la seguridad de la red sin ceder el control a la fundación. Digamos que algo sucedió y la fundación comenzó a actuar maliciosamente. Digamos que pudo crear un nuevo grupo de participación a través de una fábrica y delegar en el grupo de participación, ahora la fundación realmente no puede evitar que delegue en este grupo de participación.
Contrato de fábrica de grupo de participación
Macro include_bytes
El contrato staking_pool_factory es un contrato que internamente tiene el código de un contrato de grupo de participación. En Rust puedes hacer esto usando la macro include_bytes. Básicamente toma un archivo local y lo incrusta en el binario como un vector. Lo que sucede allí es que dentro de este binario de WebAssembly podemos tener asignada una parte de la memoria que representa un binario de este grupo de participación en particular. Volvamos a la cima.
Estructura
Una vez más esta es la estructura.
Hubo alguna información sobre el gas, volveremos a esto más tarde.
Hay una función reward_fee_fraction que se acaba de copiar del contrato del grupo de participación que revisamos antes.
Contratos Externos
Aquí están los argumentos del grupo de participación que toma, y hay características y contratos externos, por lo que esta es la interfaz de alto nivel que usamos para llamar a ciertos contratos.
Tenemos dos de ellos, el primero puede ser cualquier nombre. Lo llamamos ExtSelf, porque representa nuestro propio contrato y contiene una devolución de llamada a un método on_staking_pool_create. El segundo rasgo es para el contrato de lista blanca que acabamos de ver llamado add_staking_pool. Aquí está.
Es exactamente la misma interfaz, excepto que las características en Rust son como las interfaces en Java, por ejemplo. Simplemente definimos la interfaz de un contrato remoto.
Inicialización
Repasemos primero el escenario. Cuando se crea una fábrica, verificamos que no se haya inicializado y verificamos el staking_pool_whitelist_account_id, la identificación de la cuenta del contrato de la lista blanca, durante la inicialización de StakingPoolFactory. Necesitamos saber el staking_pool_whitelist_account_id. Aquí es donde se implementa el contrato de lista blanca para incluir en la lista blanca nuestras instancias de grupo de participación recién creadas. Recordamos esto y también creamos un conjunto de cuentas ya creadas al final de este fragmento.
Método principal
Ahora que se creó la fábrica de grupos de participación, se llama poolv1.near, por ejemplo, y la fundación ha incluido a la fábrica en la lista blanca en un contrato de lista blanca mediante la emisión de otra transacción. Ahora que esta fábrica de grupos de participación está incluida en la lista blanca, significa que tiene permiso para incluir en la lista blanca los nuevos grupos de participación que crea. Así que ahora entra un validador y quiere crear un grupo de participación para ellos mismos. La forma en que funciona es que llaman a create_staking_pool y requiere un montón de argumentos. El primer argumento es un prefijo. Digamos que es bisontrails sin el sufijo de esta identificación de cuenta actual, y esto proviene del nombre de cuenta de NEAR. Una cuenta solo puede crear una subcuenta, o una cuenta muy larga, por lo que la fábrica del grupo de participación crea una subcuenta debajo de sí misma que será bisontrails.poolv1.near. El id_propietario es el id. de la cuenta del propietario del grupo de participación, como comentamos antes. Estos tres elementos son esencialmente argumentos que pasarías a un grupo de participación cuando lo creas por primera vez. Es un argumento que puedes usar como proxy para el grupo de participación. Por ejemplo, staking_pool_id puede ser bisontrails.near. stake_public_key puede ser la clave de staking de la ejecución de un nodo de validación, y reward_fee_fraction puede ser del 10 %, por ejemplo. Tenga en cuenta que este método también es payable (Que se le adjunta dinero), lo que significa que toma un depósito adjunto entrante, y lo primero que pregunta es: “¿Adjuntaste suficiente depósito?” El depósito que debe adjuntar es 30 NEAR, y tiene muchos ceros, pero esto se debe a que está en yocto NEAR. Debe adjuntar 30 NEAR principalmente porque necesita cubrir el estado del contrato en sí en un grupo de participación recién creado. El contrato es bastante grande, tiene 250 kilobytes y necesita al menos 25 NEAR para esto, pero también se necesita algo de dinero extra para cubrir el fondo de garantía de precio. Este es uno de esos casos en los que debes tener un depósito adjunto, porque no puedes adjuntar tanta gas a esta transacción. Además, no podemos convertir gas a NEAR dentro de un contrato, por lo que, idealmente, la separación de gas permanece igual, solo se usa para el cálculo, algunas operaciones de lectura/escritura y llamadas de contratos cruzados. El saldo se utiliza para almacenamiento y transferencias. Entonces, en nuestro caso, esto creará una nueva cuenta, y la creación de una nueva cuenta en NEAR requiere que pague por el almacenamiento de esta cuenta. El almacenamiento en nuestro caso no solo va a ser la propia cuenta sino también el contrato de esta cuenta. En nuestro caso, este es el código del contrato del grupo de participación.
Lo siguiente que hacemos es verificar que el prefijo no tenga un punto, lo que significa que no es una subcuenta en sí. Luego, creamos un nuevo staking_pool_account_id concatenando el punto de identificación de nuestra cuenta (.) con este nuevo prefijo. Verificamos que la nueva identificación de la cuenta del grupo de participación sea válida. Básicamente, si alguna de estas afirmaciones falla, el Protocolo NEAR reembolsará los tokens. Si una transacción falla con un depósito adjunto, el depósito adjunto volverá al remitente o al predecesor, porque solo el predecesor puede adjuntar un saldo. Es seguro hacer un montón de afirmaciones aquí. A continuación, verificamos que el id_propietario del grupo de participación sea válido. Esto es básicamente solo un grupo de ayudas adicionales que también se verifican en el grupo de participación. Esto es para asegurarse de que si no pasa los argumentos correctos, o los argumentos ligeramente incorrectos, es mejor que falle antes de que todo se ejecute para evitar quemar gas y bloquear los tokens que gastó. Finalmente verificamos usando insert que el grupo de participación no existe. Básicamente, insert devolverá true si es un nuevo elemento único y devolverá false si el elemento ya existe en un conjunto. Así es como funciona el hashset Rust de la misma manera que funciona un conjunto ordenado. Entonces, si el nombre del grupo ya existe, no agregaremos este grupo de participación ni intentaremos crear esta cuenta nuevamente. Insert hace dos cosas: agrega este elemento al almacenamiento, y devuelve true si el elemento es único y no existía antes o devuelve false si el elemento ya está presente. Si el conjunto no tenía este valor presente, se devuelve true, si este conjunto tenía este valor presente, se devuelve false.
Finalmente, usamos una API de nivel medio, no usamos nuestros métodos de costo en bruto, pero al mismo tiempo no usamos una interfaz de alto nivel. La forma en que funciona es que creamos una nueva promesa, que crea una estructura temporal en nuestro NEAR SDK, y recuerda al receptor de esta promesa. Puedes pensar en esto como si el contrato emitiera la transacción hacia esa identificación de cuenta dada. Llamaremos a una identificación de cuenta de grupo de participación no existente. Por supuesto que no es una transacción sino un recibo, pero es un detalle técnico. Lo siguiente es la primera acción además de esta promesa. Comenzamos a agrupar acciones en esta promesa. La primera acción es create_account. Esta acción creará una nueva cuenta o fallará si la cuenta ya existe. Luego depositamos el saldo adjunto. Depositamos todo el depósito que se nos pasó, para que no lo guardemos en esta fábrica, e irá con el mismo recibo en la cuenta remota. A continuación, implementamos un contrato. Como se explicó anteriormente, include_bytes es una macro que crea un segmento estático que convertimos en un vector que pasará a una acción de implementación. Esto implementará código en la cuenta remota. Solo puedes implementar código en la cuenta que controlas, pero create_account te da permiso para actuar como si fueras el propietario de esta cuenta solo para esta transacción en particular. Puede usar el método deployment_contract, puedes stakear y hacer otras cosas en nombre de este contrato en la primera transacción que realizamos. Finalmente, inicializamos el contrato del grupo de participación utilizando la API serda. Tomamos esta estructura y la serializamos en JSON, el método se llama new. El primer argumento es el depósito adjunto a esta llamada de función. No lo necesitamos, porque no lo espera. La siguiente es la cantidad de gas que toma de su cantidad actual de gas, e inmediatamente la saca, después de lo cual va a la promesa. Digamos que nuestro método se llama 100 tera gas, tera gas es un tipo de unidad que es más o menos comprensible para los humanos.
Tienes 100 tera gas cuando ingresas y decimos que vamos a pasar la base (25 tera gas) multiplicada por 2. Inmediatamente pasaremos 50 tera gas a la llamada de función de este método, por lo que este 50 tera gas significa que estamos solo quedándonos con menos de 50 tera gas, porque ya quemamos algo en la lógica antes. Además, cada acción que incluya en esta promesa también le costará algo de gas. Por ejemplo, una acción de implementación le costará transferir los bytes de una cuenta a otra. Finalmente usamos then. Then es similar a cómo funciona javascript, adjunta la dependencia de la promesa anterior. Esta es la primera promesa, y decimos que una vez que se complete haga la segunda promesa. La forma en que funciona es que digamos que bisontrails.near llamó a este contrato (poolv1.near) para crear bisontrails.poolv1.near. Primero creamos una promesa para bisontrails.poolv1.near y luego adjuntamos una devolución de llamada a esta API, algo que no es excelente en términos de usar argumentos posicionales para dos cosas diferentes, de cualquier manera, devuelve la identificación de la cuenta actual. Entonces llamará a poolv1.near después de que se haya ejecutado la primera promesa. Aquí está la estructura: bisontrails.near llama a poolv1.near creando una promesa de grupo de participación. Ahora, esto crea una promesa para bisontrails.poolv1.near, y también crea una promesa para sí mismo en el método on_staking_pool.
Necesita el resultado de este método antes de que comience este método y pasa tres argumentos aquí. Pasa el staking_pool_account_id, el added_deposit y el predecesor_account_id. Eso es quién nos llamó, qué cuenta se intentó crear y cuántos tokens se adjuntaron en caso de que necesitemos hacer un reembolso. Ahora, si bisontrails.poolv1.near se ejecuta correctamente, on_staking_pool_create recibirá el resultado de la ejecución. Digamos que por alguna razón hubo una configuración incorrecta que también llamó a este método, entonces recibirás una devolución de llamada diciendo que falló. Devolvimos la promesa principal después, entonces significa que volvimos on_staing_pool_create primero. Es importante, porque el resultado del método create_staking_pool depende del resultado de la promesa on_staking_pool_create. La transacción no comienza completamente en paralelo, sino que ahora depende de la ejecución de este método en particular.
Llamadas de vuelta
Veamos la devolución de llamada.
Lo primero que hacemos es decir que solo puede ser llamado por el contrato actual usando assert_self, lo que significa que nadie más puede llamar a esta promesa.
Lo siguiente que hacemos es usar otro método auxiliar de utilidad que dice si la dependencia, la creación del grupo de participación, tuvo éxito o falló.
Hacemos esto de la siguiente manera: primero usamos dos métodos finales para comprobar que el número de resultados es 1. Esto es un invariante porque ya sabemos que nadie puede llamar a esto dos veces, y el segundo es si el resultado es correcto. Si el método se ejecutó con éxito, devolvemos verdadero, si la promesa falló, entonces será falso. Así que ahora sabemos sí se creó el pool de participación o no. Nuevamente, también adjuntamos 50 tera gas a la devolución de llamada, por lo que ahora estamos en la devolución de llamada, lo que significa que solo tenemos 50 tera gas.
Si tenemos éxito, registraremos que el grupo se creó correctamente y luego lo incluiremos en la lista blanca. Luego llamamos a otra promesa desde una devolución de llamada y adjuntamos 25 tera gas. Así que ahora llamamos staking_pool_whitelist_account_id, el contrato de lista blanca. Ahora, podemos incluir en la lista blanca el grupo de participación que acabamos de crear, porque pasamos este argumento hacia la devolución de llamada. Devolvemos esta promesa para que no detengamos la ejecución todavía, porque solo queremos completar la transacción completa una vez que se complete la lista blanca. Rust no tiene retorno, porque si se da el último valor sin punto y coma, entonces es un valor de retorno. Si la creación falla, puede fallar por una sola razón: si coloca una clave de ristretto no válida, como discutimos brevemente antes. Si hay alguna curva extraña en la que creó su clave de replanteo, entonces fallará. La razón por la que falla es que significa que el depósito que pasó al grupo de participación para la creación nos será reembolsado a nosotros, y no al predecesor. Tenemos 30 NEAR en este contrato, y debemos devolverlos al remitente para que no los pongamos en staking. Lo primero que hacemos es eliminarlo de la lista de staking pools que se crearon, porque esto no tuvo éxito. Entonces, estamos diciendo que la creación falló y le reembolsaremos el depósito adjunto. Ahora no es un depósito adjunto real, porque la devolución de llamada no recibe el depósito adjunto. Vuela a través de un recibo de reembolso por separado que generalmente llega antes de la devolución de llamada, y también toma el predecesor_account_id. En nuestro caso, si llamamos al predecesor_account_id, seremos nosotros porque se trata de una devolución de llamada. Necesitamos saber a quién debemos devolver los tokens, y la forma en que lo hacemos es crear una promesa usando el nuevo predecesor_account_id, y devolvemos los tokens que se adjuntaron antes. Como puede ver, no devolvemos esta promesa, solo decimos que eso es todo, no nos importa si tiene éxito o falla, el retorno básico de valor falso dice que el grupo no se pudo crear. Lo que sucede ahora es que la transacción continúa ejecutándose, pero el valor se devolverá al front-end. El front-end es NEAR CLI. Inmediatamente sabrá que la transacción ha fallado. Es posible que aún no recupere su dinero, por lo que todavía está esperando que se ejecute este reembolso en particular en el siguiente bloque, pero ya sabe que el grupo no se creó para que pueda continuar. Este es un ejemplo de cómo puedes hacer una promesa paralela. Así es como funciona un factor de grupo de participación. Aquí hay un captador que verifica cuántos grupos de participación se crearon que se pueden llamar en NEAR CLI.
Conclusión
Esto concluye la revisión del contrato NEAR Live | Parte 3: Whitelist y Staking Pool Factory. Gracias por tomarte el tiempo para aprender NEAR, pronto habrá más contenido en esta serie.