📇#[ink::contract]

A macro #[ink::contract] é o ponto de entrada para escrever contratos inteligentes ink!.

Se você é um iniciante que está tentando aprender ink!, recomendamos que você dê uma olhada no nosso extenso workshop de ink!.

Descrição

The macro does analysis on the provided smart contract code and generates proper code.

Usage

Argumentos do cabeçalho

A macro #[ink::contract] pode receber argumentos de cabeçalho adicionais separados por vírgulas:

compile_as_dependency: bool

Indica ao gerador de código ink! para sempre compilar ou nunca compilar o smart contract como se fosse usado como uma dependência de outro smart contract ink!.

Normalmente, essa sinalização só é realmente útil para desenvolvedores ink! que desejam inspecionar a geração de código de smart contracts ink!. O autor não tem conhecimento de nenhum caso de uso prático específico para usuários que faça uso desse sinalizador, mas os escritores de contratos são encorajados a refutar essa afirmação.

Observe que é recomendado usar o recurso embutido da crate ink-as-dependency para marcar as dependências de smart contracts listadas no Cargo.toml de um contrato como dependências reais para ink!.

Exemplo de Uso:

#[ink::contract(compile_as_dependency = true)]
mod my_contract {
    #[ink(storage)]
    pub struct MyStorage;

    impl MyStorage {
        #[ink(constructor)]
        pub fn construct() -> Self { MyStorage {} }

        #[ink(message)]
        pub fn message(&self) {}
    }
    // ...
}

Valor padrão: Dependente da propagação do recurso do arquivo Cargo.toml.

env: impl Environment

Indica ao gerador de código ink! qual ambiente utilizar para o smart contract ink!. O ambiente deve implementar o traço Environment (definido em ink_env) e fornecer todas as definições de tipos fundamentais necessárias, como Balance, AccountId, etc.

Ao usar uma implementação personalizada de Environment para um smart contract, todos os tipos que ele expõe para o smart contract ink! e os tipos espelhados usados no tempo de execução devem estar alinhados em relação à codificação SCALE e semântica.

Usage Example:

Dada uma implementação personalizada do ambiente Environment:

pub struct MyEnvironment;

impl ink::env::Environment for MyEnvironment {
    const MAX_EVENT_TOPICS: usize = 3;

    type AccountId = u64;
    type Balance = u128;
    type Hash = [u8; 32];
    type Timestamp = u64;
    type BlockNumber = u32;
    type ChainExtension = ::ink::env::NoChainExtension;
}

Um usuário pode implementar seu contrato ink! usando a implementação personalizada do Ambiente conforme demonstrado abaixo:

#[ink::contract(env = MyEnvironment)]
mod my_contract {
    pub struct MyEnvironment;

    impl ink::env::Environment for MyEnvironment {
        const MAX_EVENT_TOPICS: usize = 3;
        type AccountId = u64;
        type Balance = u128;
        type Hash = [u8; 32];
        type Timestamp = u64;
        type BlockNumber = u32;
        type ChainExtension = ::ink::env::NoChainExtension;
    }

    #[ink(storage)]
    pub struct MyStorage;

    impl MyStorage {
        #[ink(constructor)]
        pub fn construct() -> Self { MyStorage {} }

        #[ink(message)]
        pub fn message(&self) {}
    }
    // ...
}

Valor padrão: DefaultEnvironment definido no pacote ink_env.

Análise

A macro #[ink::contract] realiza uma análise completa do contrato inteligente de entrada em relação a argumentos inválidos e estrutura.

Algumas regras de exemplo incluem, mas não se limitam a:

  • Deve haver exatamente uma estrutura #[ink(storage)]. Essa estrutura define o layout do armazenamento em que o contrato inteligente ink! opera. O usuário pode utilizar uma variedade de recursos integrados, combiná-los de várias maneiras ou até mesmo fornecer suas próprias implementações de estruturas de dados de armazenamento. Para mais informações, visite a documentação do crate ink_storage.

Exemplo:

#[ink::contract]
mod flipper {
    #[ink(storage)]
    pub struct Flipper {
        value: bool,
    }

    impl Flipper {
        #[ink(constructor)]
        pub fn construct() -> Self { Flipper { value: false } }

        #[ink(message)]
        pub fn message(&self) {}
    }
}
  • Deve haver pelo menos um método #[ink(constructor)] definido. Os métodos marcados com #[ink(constructor)] são especiais, pois são despacháveis ​​ao instanciar o contrato. Um contrato pode definir vários construtores desse tipo, o que permite que os usuários do contrato instanciem o contrato de várias maneiras diferentes.

Exemplo: Dada a definição do contrato Flipper acima, adicionamos um #[ink(constructor)] da seguinte maneira:

#[ink::contract]
mod flipper {
    #[ink(storage)]
    pub struct Flipper {
        value: bool,
    }

    impl Flipper {
        #[ink(constructor)]
        pub fn new(initial_value: bool) -> Self {
            Flipper { value: false }
        }

        #[ink(message)]
        pub fn message(&self) {}
    }
}
  • Deve haver pelo menos um método #[ink(message)] definido. Os métodos marcados com #[ink(message)] são especiais, pois são despacháveis ​​ao invocar o contrato. O conjunto de mensagens ink! definidas para um contrato ink! define a sua interface de programação de aplicação (API), com a qual os usuários podem interagir. Um contrato ink! pode ter vários métodos ink! definidos.

Nota: Uma mensagem ink! com um receptor &self só pode ler o estado, enquanto uma mensagem ink! com um receptor &mut self pode alterar o armazenamento do contrato.

Exemplo

Dado a definição do contrato Flipper acima, adicionamos algumas definições #[ink(message)] da seguinte forma:

#[ink::contract]
mod flipper {
    #[ink(storage)]
    pub struct Flipper {
        value: bool,
    }

    impl Flipper {
        #[ink(constructor)]
        pub fn new(initial_value: bool) -> Self {
            Flipper { value: false }
        }

        /// Flips the current value.
        #[ink(message)]
        pub fn flip(&mut self) {
            self.value = !self.value;
        }

        /// Returns the current value.
        #[ink(message)]
        pub fn get(&self) -> bool {
            self.value
        }
    }
}

Mensagens Pagáveis:

Uma mensagem ink! por padrão rejeitará chamadas que adicionem fundos ao contrato inteligente. Os autores de contratos ink! podem tornar uma mensagem ink! pagável adicionando o sinalizador "payable" a ela. Um exemplo abaixo:

Observe que os construtores ink! são sempre implicitamente pagáveis e, portanto, não podem ser marcados como tal.

#[ink::contract]
mod flipper {
    #[ink(storage)]
    pub struct Flipper {
        value: bool,
    }

    impl Flipper {
        #[ink(constructor)]
        pub fn new(initial_value: bool) -> Self {
            Flipper { value: false }
        }

        /// Flips the current value.
        #[ink(message)]
        #[ink(payable)] // You can either specify payable out-of-line.
        pub fn flip(&mut self) {
            self.value = !self.value;
        }

        /// Returns the current value.
        #[ink(message, payable)] // ...or specify payable inline.
        pub fn get(&self) -> bool {
            self.value
        }
    }
}

Controlando o seletor das mensagens:

Cada mensagem e construtor do ink! possui um seletor único que identifica de forma exclusiva a mensagem ou o construtor dentro do smart contract ink!. Esses seletores são usados principalmente para direcionar a chamada do contrato ao executá-lo.

O autor de um smart contract ink! pode controlar o seletor de uma mensagem ou construtor usando a flag selector. Um exemplo é mostrado abaixo:

#[ink::contract]
mod flipper {
    #[ink(storage)]
    pub struct Flipper {
        value: bool,
    }

    impl Flipper {
        #[ink(constructor)]
        #[ink(selector = "0xDEADBEEF")] // Works on constructors as well.
        pub fn new(initial_value: bool) -> Self {
            Flipper { value: false }
        }

        /// Flips the current value.
        #[ink(message)]
        #[ink(selector = "0xCAFEBABE")] // You can either specify selector out-of-line.
        pub fn flip(&mut self) {
            self.value = !self.value;
        }

        /// Returns the current value.
        #[ink(message, selector = "0xFEEDBEEF")] // ...or specify selector inline.
        pub fn get(&self) -> bool {
            self.value
        }
    }
}

Interacting with the Contract Executor

A crate ink_env fornece recursos para interagir com o executor do contrato que conecta os contratos inteligentes ink! com o mundo exterior.

Por exemplo, é possível consultar o chamador da chamada atual através de:

use ink_env;
ink::env::test::run_test::<ink::env::DefaultEnvironment, _>(|_| {
    let caller = ink::env::caller::<ink::env::DefaultEnvironment>();
    let _caller = caller;
    Ok(())
}).unwrap();

No entanto, ink! fornece uma maneira muito mais simples de interagir com o executor do contrato por meio do seu acessor de ambiente. Um exemplo abaixo:

#[ink::contract]
mod greeter {
    #[ink(storage)]
    pub struct Greeter;

    impl Greeter {
        #[ink(constructor)]
        pub fn new() -> Self {
            let caller = Self::env().caller();
            let message = format!("thanks for instantiation {:?}", caller);
            ink::env::debug_println(&message);
            Greeter {}
        }

        #[ink(message, payable)]
        pub fn fund(&mut self) {
            let caller = self.env().caller();
            let value = self.env().transferred_balance();
            let message = format!("thanks for the funding of {:?} from {:?}", value, caller);
            ink::env::debug_println(&message);
        }
    }
}

Eventos

Um contrato inteligente ink! pode definir eventos que podem ser emitidos durante a execução do contrato. A emissão de eventos pode ser usada por ferramentas de terceiros para consultar informações sobre a execução e estado de um contrato.

O exemplo a seguir mostra um contrato ink! que define e emite um evento "Transferred" no #[ink(constructor)].

 #[ink::contract]
 mod erc20 {
     /// Defines an event that is emitted every time value is transferred.
     #[ink(event)]
     pub struct Transferred {
         from: Option<AccountId>,
         to: Option<AccountId>,
         value: Balance,
     }

     #[ink(storage)]
     pub struct Erc20 {
         total_supply: Balance,
         // more fields ...
     }

     impl Erc20 {
         #[ink(constructor)]
         pub fn new(initial_supply: Balance) -> Self {
             let caller = Self::env().caller();
             Self::env().emit_event(Transferred {
                 from: None,
                 to: Some(caller),
                 value: initial_supply,
             });
             Self { total_supply: initial_supply }
         }

         #[ink(message)]
         pub fn total_supply(&self) -> Balance {
             self.total_supply
         }
     }
 }

Neste exemplo, o módulo erc20 define um contrato inteligente ink! para uma token ERC20 básica. O evento "Transferred" é definido como uma estrutura que contém informações sobre o remetente, destinatário e valor transferido. No construtor, o evento é emitido quando o contrato é instanciado, informando que uma transferência ocorreu do endereço do contrato para o endereço do chamador. O contrato também possui um método total_supply que retorna o valor total de fornecimento do token.

Exemplo: Flipper

O código abaixo mostra a implementação completa do contrato inteligente Flipper ink!. Para nós, ele funciona como o "Olá, mundo!" dos contratos inteligentes ink!, pois é mínimo, mas ainda fornece alguma funcionalidade mais ou menos útil.

Ele controla um único valor bool que pode ser false ou true e permite que o usuário inverta esse valor usando a mensagem Flipper::flip ou recupere o valor atual usando Flipper::get.

#[ink::contract]
pub mod flipper {
    #[ink(storage)]
    pub struct Flipper {
        value: bool,
    }

    impl Flipper {
        /// Creates a new flipper smart contract initialized with the given value.
        #[ink(constructor)]
        pub fn new(init_value: bool) -> Self {
            Self { value: init_value }
        }

        /// Flips the current value of the Flipper's bool.
        #[ink(message)]
        pub fn flip(&mut self) {
            self.value = !self.value;
        }

        /// Returns the current value of the Flipper's bool.
        #[ink(message)]
        pub fn get(&self) -> bool {
            self.value
        }
    }
}

O código abaixo mostra a implementação completa do contrato inteligente ink! chamado Flipper. Para nós, ele atua como o "Olá, mundo!" dos contratos inteligentes ink!, pois é mínimo, mas ainda fornece alguma funcionalidade mais ou menos útil. Ele controla um único valor booleano que pode ser falso ou verdadeiro e permite que o usuário inverta esse valor usando a mensagem Flipper::flip ou obtenha o valor atual usando a mensagem Flipper::get.

Last updated