Hoy voy a hablar de uno de los patrones de diseño que más habitualmente se utilizan en la programación de contratos inteligentes y que es muy útil puesto que lo tendremos que usar casi seguro en cualquier aplicación Blockchain que desarrollemos, de hecho a mi siempre me recuerda al patrón Singleton en POO, por lo típico que es su utilización. Me estoy refiriendo al patrón Control de Acceso o Access Control.
El patrón de control de acceso es una técnica utilizada en la programación de contratos inteligentes para controlar el acceso a las funciones y datos almacenados en el contrato. El objetivo de este patrón es asegurar que solamente las direcciones de Ethereum (o la cadena que estemos utilizando) autorizadas puedan ejecutar ciertas funciones y acceder a información sensible.
Existen varias formas de implementar el patrón de control de acceso en un contrato inteligente. Una de las formas más comunes es utilizando modificadores. Los modificadores son funciones que se ejecutan antes de la función principal y que verifican ciertas condiciones para permitir o denegar el acceso a la función. Por ejemplo, se puede crear un modificador que compruebe si la dirección que llama a una función está autorizada a realizar esa acción.
Aquí hay un ejemplo sencillo de cómo podría ser la implementación del patrón de control de acceso utilizando modificadores:
contract MiContrato {
address public owner;
mapping (address => bool) public authorized;
constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
modifier onlyAuthorized() {
require(authorized[msg.sender]);
_;
}
function setAuthorized(address _address, bool _value) public onlyOwner {
authorized[_address] = _value;
}
function realizarAccion() public onlyAuthorized {
// hacer algo
}
}
En este ejemplo, el contrato MiContrato
tiene una variable owner
que se establece en el constructor con la dirección que creó el contrato. También tiene un mapeo authorized
que mantiene un registro de las direcciones autorizadas para llamar a ciertas funciones.
El modificador onlyOwner
verifica si la dirección que llama a la función es igual a la dirección del propietario del contrato y solo permite que el propietario ejecute funciones con este modificador.
El modificador onlyAuthorized
comprueba si la dirección que llama a la función está en el mapeo authorized
y solo permite que las direcciones autorizadas ejecuten la función con este modificador.
La función setAuthorized
permite al propietario del contrato agregar o eliminar direcciones autorizadas para llamar a la función realizarAccion
. La función realizarAccion
solo se puede ejecutar si la dirección que llama a la función está autorizada.
Pero como decía, no solamente se puede usar este patrón en nuestros contratos en Solidity, por ejemplo, en Polkadot. La plataforma Polkadot es una cadena de bloques multi-cadena que permite la interoperabilidad entre diferentes cadenas de bloques. Por lo tanto, en Polkadot, los contratos inteligentes se ejecutan en diferentes cadenas de bloques, cada una con su propia lógica y reglas lo que la convierte en una de las cadenas más versátiles de las disponibles.
En Polkadot, el patrón de control de acceso se puede implementar de diferentes maneras, dependiendo de la cadena de bloques que se esté utilizando. Por ejemplo, en una cadena de bloques como Kusama, que utiliza el lenguaje de programación Rust, se puede implementar el patrón de control de acceso utilizando los mecanismos de tipos y estructuras de datos de Rust.
Aquí hay un ejemplo simple de cómo se puede implementar el patrón de control de acceso en un contrato inteligente en Polkadot utilizando Rust:
use ink_lang::contract;
#[contract]
mod mi_contrato {
use ink_storage::collections::HashMap;
#[ink(storage)]
pub struct MiContrato {
owner: AccountId,
authorized: HashMap<AccountId, bool>,
}
impl MiContrato {
#[ink(constructor)]
pub fn new(&mut self) {
self.owner = self.env().caller();
}
#[ink(message)]
pub fn set_authorized(&mut self, account: AccountId, value: bool) {
self.authorized.insert(account, value);
}
#[ink(message)]
pub fn realizar_accion(&self) {
assert!(self.authorized.get(&self.env().caller()) == Some(&true));
// hacer algo
}
}
}
En este ejemplo, el contrato MiContrato
tiene una variable owner
que se establece en el constructor con la dirección que creó el contrato. También tiene un mapa authorized
que mantiene un registro de las direcciones autorizadas para llamar a ciertas funciones.
La función set_authorized
permite al propietario del contrato agregar o eliminar direcciones autorizadas para llamar a la función realizar_accion
. La función realizar_accion
solo se puede ejecutar si la dirección que llama a la función está autorizada.
La función realizar_accion
utiliza la macro assert!
para verificar que la dirección que llama a la función está en el mapa authorized
y tiene el valor true
. Si la verificación falla, la función se detendrá con un error.
En resumen, el patrón de control de acceso también se puede aplicar en los contratos inteligentes en Polkadot utilizando los mecanismos de tipos y estructuras de datos de Rust. Los contratos inteligentes en Polkadot pueden utilizar diferentes lenguajes de programación y mecanismos para implementar el patrón de control de acceso, dependiendo de la cadena de bloques y la plataforma utilizada.
Os dejo aquí la lista de post relacionados con este tema, por si queréis darle un vistazo:
- Patrones de Diseño para Contratos Inteligentes (Parte 2)
- Patrones de Diseño para Contratos Inteligentes (Parte 1)
Pues por hoy nada más, hasta la próxima blockckainers 😉