#[ink::chain_extension]
Last updated
Last updated
Na configuração padrão do contracts-pallet
, um contrato inteligente só pode interagir com a runtime por meio de sua interface básica bem definida. Essa API já permite uma variedade de interações entre o contracts-pallet
e o contrato inteligente em execução. Por exemplo, é possível chamar e instanciar outros contratos inteligentes na mesma cadeia, emitir eventos, consultar informações de contexto ou executar procedimentos de hash criptográfico incorporados.
Se esse conjunto básico de recursos não for suficiente para atender às necessidades de uma determinada blockchain construída com Substrate, é possível estender facilmente essa API usando o recurso chamado chain extension.
Com as chain extensions, você pode expor partes da lógica da sua runtime aos desenvolvedores de contratos inteligentes.
NOTA: O repositório do ink! contém o exemplo rand-extension
. Este é um exemplo completo de uma chain extension implementada tanto em ink! quanto no Substrate.
A interface consiste em um código de erro que indica erros leves, bem como a definição de alguns métodos de chain extension.
A estrutura geral segue a de uma simples definição de trait em Rust. O código de erro é definido como uma definição de tipo associado da definição do trait. Os métodos são definidos como métodos associados do trait sem implementação.
Os métodos de chain extension não devem ter um receptor self
, como &self
ou &mut self
, e devem ter entradas e saída que implementam o codec SCALE. O valor de retorno segue regras específicas que podem ser alteradas usando o atributo handle_status
e a alternância entre tipos Result
e Non-Result
, que são descritos com mais detalhes abaixo.
Normalmente, a definição de chain extension usando essa macro é fornecida pelo autor da chain extension em uma crate separada. Contratos inteligentes ink! que utilizam essa chain extension simplesmente dependem dessa crate e usam sua definição de ambiente associada para utilizar os métodos fornecidos pela chain extension.
Existem dois atributos diferentes com os quais os métodos de chain extension podem ser marcados:
ink(extension = N: u32)
Yes
-
Determines the unique function ID of the chain extension method.
ink(handle_status = flag: bool)
Optional
true
Assumes that the returned status code of the chain extension method always indicates success and therefore always loads and decodes the output buffer of the call.
Como acontece com todos os atributos do ink!, vários deles podem aparecer em uma lista contígua:
...ou como vários atributos individuais do ink! aplicados ao mesmo item:
handle_status
Valor padrão: true
Por padrão, todos os métodos de chain extension devem retornar um Result<T, E>
onde E: From<Self::ErrorCode>
. O Self::ErrorCode
representa o código de erro da chain extension. Isso significa que um contrato inteligente que chama um método de chain extension primeiro consulta o código de status retornado pelo método de chain extension e só carrega e decodifica a saída se o código de status indicar uma chamada bem-sucedida. Essa abordagem foi escolhida por ser mais eficiente quando nenhuma saída além do código de erro é necessária para uma chamada de chain extension. Ao projetar uma chain extension, tente utilizar o código de erro para retornar erros e use o buffer de saída apenas para informações que não se encaixam em um único valor u32
.
Um método de chain extension que é marcado com handle_status = false
assume que o código de erro retornado sempre indicará sucesso. Portanto, ele sempre carregará e decodificará o buffer de saída e perderá a restrição E: From<Self::ErrorCode
para a chamada.
Observe que se um método de chain extension não retornar Result<T, E>
onde E: From<Self::ErrorCode>
, mas handle_status = true
, ele ainda retornará um valor do tipo Result<T, Self::ErrorCode>
.
Utilize tanto handle_status = false
quanto um tipo de retorno non-Result
para o mesmo método de chain extension se uma chamada a ele nunca pode falhar e nunca retorna um tipo Result
.
Devido à possibilidade de marcar um método de chain extension comhandle_status
e a escolha entre (1) retorna apenas Result<T, E>
ou (2) retornar apenas T
, existem 4 casos diferentes com semânticas ligeiramente distintas:
handle_status
Returns Result<T, E>
Effects
true
true
O método de extensão de cadeia é obrigado a retornar um valor do tipoResult<T, E>
onde E: From<Self::ErrorCode>
. Uma chamada sempre verificará se o código de status retornado indica sucesso e somente então carregará e decodificará o valor no buffer de saída.
true
false
O método de extensão de cadeia pode retornar qualquer tipo non-Result
. Uma chamada sempre verificará se o código de status retornado indica sucesso e somente então carregará e decodificará o valor no buffer de saída. O tipo de retorno real do método de extensão de cadeia ainda é Result<T, Self::ErrorCode>
quando o método de extensão de cadeia foi definido para retornar um valor do tipo T
.
false
true
O método de extensão de cadeia deve retornar um valor do tipo Result<T, E>
. Uma chamada sempre assume que o código de status retornado indica sucesso e, portanto, sempre carrega e decodifica o buffer de saída diretamente.
false
false
O método de extensão de cadeia pode retornar qualquer tipo non-Result
. Uma chamada sempre assume que o código de status retornado indica sucesso e, portanto, sempre carrega e decodifica diretamente o buffer de saída.
Cada chain extension define exatamente um ErrorCode
usando a seguinte sintaxe:
The defined ErrorCode
must implement FromStatusCode
which should be implemented as a more or less trivial conversion from the u32
status code to a Result<(), Self::ErrorCode>
. The Ok(())
value indicates that the call to the chain extension method was successful.
By convention an error code of 0
represents success. However, chain extension authors may use whatever suits their needs.
In the below example a chain extension is defined that allows its users to read and write from and to the runtime storage using access privileges:
All the error types and other utility types used in the chain extension definition above are often required to implement various traits such as SCALE's Encode
and Decode
as well as scale-info
's TypeInfo
trait.
A full example of the above chain extension definition can be seen here.
In order to allow ink! smart contracts to use the above defined chain extension it needs to be integrated into an Environment
definition as shown below:
Above we defined the CustomEnvironment
which defaults to ink!'s DefaultEnvironment
for all constants and types but the ChainExtension
type which is assigned to our newly defined chain extension.
An ink! smart contract can use the above defined chain extension through the Environment
definition defined in the last example section using the env
macro parameter as shown below.
Note that chain extension methods are accessible through Self::extension()
or self.extension()
. For example as in Self::extension().read(..)
or self.extension().read(..)
.
Due to technical limitations it is not possible to refer to the ErrorCode
associated type using Self::ErrorCode
anywhere within the chain extension and its defined methods. Instead chain extension authors should directly use the error code type when required. This limitation might be lifted in future versions of ink!.
It is not possible to declare other chain extension traits as super traits or super chain extensions of another.