French Solana dev #1: Développer un program (smart-contract) sur la blockchain Solana avec le framework Anchor ⚓️🧑‍💻
Tags: tutorial, blockchain, solana, rust
Published: 2022-04-05 23:29:46

## Préambule

Assez récemment, je me suis rendu compte du manque de ressources écrites en français dans le domaine du développement et du web3.

Même si de plus en plus de ressources commencent à voir le jour pour l’écriture de smart-contracts avec le populaire Solidity, il n’en est pas de même pour les blockchains n’étant pas EVM-compatible.

En effet, ces blockchains utilisent toutes des architectures différentes, et n’étant pas aussi populaire qu’Ethereum à l’heure où j’écris ces lignes, il est plus compliqué de mettre la main sur de bonnes ressources d’apprentissage dans sa langue d’origine.

Alors si vous êtes allergiques à l’anglais, je vous invite à lire cet article qui, j’espère, pourra vous accompagner et vous aider à comprendre le fonctionnement d’un *program* sur Solana 🙂

---

## Pré-requis

Nous utiliserons le langage Rust pour écrire notre *program*, je vous conseille donc d’avoir déjà les bases du langage. [The Rust Book](https://doc.rust-lang.org/book/) est la ressource de référence pour se familiariser avec Rust (aussi disponible "partiellement" en français !).

Il est évidemment primordial que vous soyez déjà familier avec l’architecture d’une blockchain ainsi que son fonctionnement.

Vous devrez aussi lire la [documentation *developer* de Solana](https://docs.solana.com/developing/programming-model/overview) pour bien comprendre de ce que l’on parle, même si je reviendrai sur certains points pour essayer d’apporter une vision plus vulgarisé.

-----

## 📦 Installations

### Rust

Vous pouvez installer Rust à l'aide de ces trois commandes:

```shell
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
rustup component add rustfmt
```
Pour une installation plus détaillée, référez vous au [Rust Book](https://doc.rust-lang.org/book/ch01-01-installation.html).

### Solana

Le client solana vous permet d’interagir avec les différents réseaux Solana, de générer et gérer vos différents comptes (*accounts*) et divers autres utilités...

Sur macOS et Linux:
```shell
sh -c "$(curl -sSfL https://release.solana.com/v1.10.4/install)"
```
L’installation détaillée est disponible sur la [documentation de Solana](https://docs.solana.com/cli/install-solana-cli-tools#use-solanas-install-tool).

Pour la suite, vous aurez besoin d’un *account* pour l’utiliser avec anchor.
🔑 `solana-keygen` nous permet de générer une paire de clés publique/privée:
```shell
solana-keygen new
```

### Yarn

Yarn est utilisé par Anchor. Si vous ne l’avez pas sur votre machine, il est possible de l’installer via NPM:
```shell
npm i -g corepack
```

### Anchor

[Anchor](https://github.com/project-serum/anchor) est un *framework* simplifiant énormément la vie des développeurs sur Solana et contenant une panoplie de *features* telles que:

- Des *crates* et une librairie Rust
- Une [IDL](https://en.wikipedia.org/wiki/Interface_description_language) complète pour nos *programs*
- Un *package* TypeScript pour utiliser nos *programs* avec l’IDL
- une CLI et un gestionnaire d’espace de travail pour développer des applications du backend au frontend

Il ne s’agit ici que de créer notre *program*. Les testes et l’interaction avec notre *program* déployé en front étant plus propice à une future partie.

Anchor est comparable à [Truffle](https://trufflesuite.com/) ou bien [Hardhat](https://hardhat.org/), les deux *frameworks* les plus utilisés pour travailler sur des *smart-contracts* en Solidity.

Pour installer anchor sur votre machine, il est préférable d’utiliser le gestionnaire de version d’anchor (AVM).

Celui-ci est à installer via cargo:
```bash
cargo install --git https://github.com/project-serum/anchor avm --locked --force
```

Vous pouvez ensuite installer la dernière version d’anchor via l’avm:

```shell
avm install latest
avm use latest
```

Pour vérifier qu’anchor est bien installé:

```shell
anchor --version
```

D’autres méthodes d’installation sont disponibles sur l’[anchor book](https://book.anchor-lang.com/chapter_2/installation.html#installation).

---

## Création du projet

Pour créer un nouveau projet anchor, il suffit d’utiliser la commande suivante:

```shell
anchor init <nom-du-projet>
```

Cela crée un dossier avec le nom de votre projet passé en argument et une base de projet à partir de laquelle vous pouvez commencer à travailler.

---

## Structure d’un projet Anchor

Il est important de comprendre les fichiers et dossiers que compose un projet Anchor:

- Le dossier `.anchor` contient un réseau local ainsi que divers *logs* liés à celui-ci.
- Le dossier `app` peut accueillir le front-end lié à vos programs si vous désirer travailler dans un seul *repository*.
- Le dossier `migrations` contient nos scripts de migrations et de déploiement.
- Le dossier `programs` contient tout nos programs. En effet, on peut écrire de multiples *programs* pour notre projet. Notez qu’anchor a déjà créé un *program* avec le nom de votre projet, qui contient un code minimaliste d’exemple dans `lib.rs`.
- Le dossier `target` est typique à Rust et contient tous les *builds* et les fichiers compilés. Pas besoin de toucher à ce dossier dans 99% des cas.
- Le dossier `tests` contient tout nos scripts écrits pour tester nos *programs*.

A la racine se situe aussi le fichier de configuration d’anchor `Anchor.toml` contenant une configuration de base:

- [programs.localnet] contient les IDs de nos différents _programs_, nous y reviendront juste après 🙂
- [registry] vous permet de *push* votre projet vers un registre de *programs*.
- [provider] contient le réseau à utiliser pour exécuter vos scripts de tests ainsi que l’*account* à utiliser.
- [scripts] contient la commande que `anchor test` exécute pour vos scripts de tests

---

## Structure d’un _program_

Un _program_ Solana écrit avec Anchor se compose en plusieurs parties distinctes:

- Une macro *`declare_id!`* qui définit l’ID de notre *program*.
- Un module comportant l’attribut `#[program]` où est définit tous les points d’entrées (*entrypoints*) de notre *program*. Une *entrypoint* est une fonction que l’on peut appeler de l’extérieur, dans une transaction, par exemple par RPC.
On appelle plus fréquemment ces fonctions des **Insctructions** et elles modifient l’état de la blockchain.
- Des structures implémentant l’attribut `#[derive(Accounts)]` qui définissent tous les *accounts* dont une instruction a besoin. Ces structures sont alors passées dans le `Context` de nos instructions.
- Des structures implémentant l’attribut `#[account]` vous permettent de stocker des données dans votre *program*.
Je rappelle que toutes les données sont stockées sous la forme d’un *account* et donc que chaque données ont au moins une clé publique. Nous y reviendront par la suite.

Pour la suite, on travaillera sur la base d’un *program* ayant pour but de définir une identité pour chaque utilisateur. Un utilisateur pourra créer son identité, et ensuite modifier certaines parties de son identité. Il pourra aussi supprimer son identité de la blockchain, mais seulement 2 ans après sa création !

---

## Analyse du _program_ d’exemple

Après avoir créé votre projet anchor, vous pouvez vous rendre dans votre premier program qui a été généré avec le nom de votre projet dans le chemin suivant:

`/programs/<nom-du-projet>/src/lib.rs`

`lib.rs` comporte déjà un code d’exemple minime qui devrait ressembler au code suivant:

```rust
use anchor_lang::prelude::*;

/// Notre macro de déclaration d'ID pour notre program
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

/// Notre module définissant les différentes instructions de notre program
#[program]
pub mod identity {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        Ok(())
    }
}

/// Une structure comportant les accounts à passer dans le Context de notre instruction
#[derive(Accounts)]
pub struct Initialize {}
```

Comme vous pouvez le remarquer, nous avons trois des quatre parties que je vous ai présentés juste au dessus. Sachant qu’un program n’est pas obligé de stocker des données, nous n’avons pas de structures implémentant l’attribut `#[account]` pour le moment.

Ce _program_ d’exemple comporte un point d’entrée, l’instruction `initialize()`.
Toutes les instructions reçoivent en paramètres au moins un *`Context<T>`* contenant des données sur le contexte actuel. `T` étant une structure comportant l’attribut `#[derive(Accounts)]`.

Une instruction retourne un `Result<T, Error>`. `Result` est un élément typique de Rust, qui peut comporter dans notre cas un `Ok<T>`, qui est renvoyé si notre instruction réussit, ou sinon un `Err(Error)` en cas d’échec. Ici, `Error` est le type d’erreur que Anchor fournit. (Nous verrons comment faire nos propres erreurs plus tard).

Cette instruction ne fait donc rien hormis renvoyer un `Ok(())` pour signaler la réussite de notre instruction (heureusement vu qu’elle ne fait rien...)

---

## Modification de l’ID de notre program

Quand anchor génère ce _program_, l’ID fournit au _program_ est toujours le même:

```rust
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS")
```

Bien que pour le moment, cela ne dérange pas, mieux vaut que notre _program_ soit unique et donc, que l’ID soit aussi unique !

Pour ce faire, nous allons modifier cet ID par la clé publique de l’_account_ généré pour notre program.

En effet, anchor nous génère déjà un _account_ avec sa paire de clé publique/privé pour chaque program que l’on crée.
Ces _accounts_ sont accessibles en tapant la commande suivante:

```shell
anchor keys list
```

Vous devriez voir apparaitre votre program ainsi que sa clé publique.

```shell
❯ anchor keys list
identity: GxyJLSDuC7BkeorKoMg87uhaXvuaUxDjDKT7iQWCbxXJ
```

Je pense que vous l’aviez deviné, on va remplacer l’ID par défaut de notre program par la clé publique de l’_account_ généré pour notre program.

```rust
declare_id!("GxyJLSDuC7BkeorKoMg87uhaXvuaUxDjDKT7iQWCbxXJ");
```

Il faut aussi remplacer l’ID par défaut dans le fichier de configuration d’anchor, `Anchor.toml` à la racine de notre projet:

```toml
[programs.localnet]
identity = "GxyJLSDuC7BkeorKoMg87uhaXvuaUxDjDKT7iQWCbxXJ"
```

Une fois cela fait, on peut commencer de rentrer dans le vif du sujet 😉

---

## Définition d’une identité

Avant de commencer à travailler sur nos différentes instructions et nos différents contexts, il est plus cohérent de définir notre structure `Identity` qui définira comment une identité est stocké par notre _program_.

Etant assez maniaque sur l’organisation du code, je conseille de séparer la définition de notre identité dans un fichier différent. Pour ma part, ce sera dans un module que j’ai nommé `identites.rs`.

Pour importer notre nouveau module dans notre _program_ et pouvoir utiliser son contenu:

```rust
mod identites;
```

Si vous vous rappelez bien, on définit une structure stockant des données avec l’attribut `#[account]`:

```rust
use anchor_lang::prelude::*;

/// Définit la structure d'une identité d'un utilisateur
#[account]
pub struct Identity {}
```

On souhaite qu’une identité contienne un prénom, un nom, un pseudonyme, sa date de naissance, sa date de création et l’utilisateur peut aussi spécifier une adresse mail sans être obligatoire.

Notre structure se définit donc comme suit:

```rust
/// Définit la structure d'une identité d'un utilisateur
#[account]
pub struct Identity {
    pub first_name: String, // max 128 bytes
    pub last_name: String,  // max 128 bytes
    pub username: String,   // max 128 bytes
    pub birth: i64,
    pub mail: Option<String>, // max 128 bytes
    pub created: i64
}
```

Plusieurs remarques:

- Les champs contenant une chaine de caractères doivent avoir une taille maximum prédéfinie, bien que le type `String` de Rust ne soit pas forcément limité, c’est à nous de définir un maximum, nous verrons pourquoi juste après !
Dans notre cas, sachant qu’un caractère encodé en UTF-8 [peut mesurer 1 à 4 bytes](https://en.wikipedia.org/wiki/UTF-8#Encoding), 128 bytes nous assure 32 caractères dans le pire des cas.
- Les champs `birth` et `created` contiennent une date sous la forme d’un Unix _timestamp_. Pour une optimisation de taille, `i32` serait préférable à `i64`, mais `i32` est dangereux et pourrait rendre le program inutilisable le jour où l’unix _timestamp_ dépasse la limite de taille d’un `i32` (en l’an 2038...). `i64` nous assure une utilisation sans problème jusqu’en l’an 2262 😃 !
- Notre champ `mail` étant optionnel, le typique `Option<T>` de Rust est le plus adapté ici.

Il manque un point important à aborder pour nos accounts de données... l’espace fixé !

---

## La taille de notre structure identité

Si vous vous rappelez, chaque _account_ sur la blockchain Solana est initialisé avec une taille maximum prédéfinie, ce qui permet à la blockchain de savoir à combien s’élève le montant à devoir payer où à garder sur l’account pour être “[rent-exempt](https://docs.solana.com/implemented-proposals/rent)”.

❗Même si vous n’utilisez pas tout l’espace disponible sur un _account_, vous payez quand même pour le maximum ! Il faut donc faire attention à régler une taille maximum cohérente sur vos _accounts_ de données pour ne pas faire fuir vos utilisateurs...

Il faut déterminer la taille maximum que peut prendre une identité, et pour cela, il faut se référencer au tableau suivant disponible sur le [Anchor Book](https://book.anchor-lang.com/chapter_5/space.html):

![spaces array](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bow49f5ut99gjorit73h.png)
 

Par rapport à notre structure `Identity`, cela nous donne:

```rust
/// Définit la structure d'une identité d'un utilisateur
#[account]
pub struct Identity {
    pub first_name: String, // 128 + 4 = 132
    pub last_name: String,  // 128 + 4 = 132
    pub username: String,   // 128 + 4 = 132
    pub birth: i64, // 8
    pub mail: Option<String>, // 128 + 1 = 129
    pub created: i64 // 8
}
```

Pour finir, on peut placer ces données dans des constantes de notre structure `Identity`:
```rust
impl Identity {
    pub const MAX_STRING_SIZE: usize = 128;
    pub const MAX_IDENTITY_SIZE: usize = 132 + 132 + 132 + 8 + 129 + 8;
}
```

---

## Définition de nos entrypoints

On va ensuite définir nos différentes instructions pour gérer notre identité.

On peut supprimer l’instruction `initialize()` d’exemple car nous ne l’utiliserons pas.

En reprenant le cahier des charges que j’ai énoncé plus haut, j’ai défini toutes ces instructions:
```rust
#[program]
pub mod identity {
    use super::*;
    
    /// Permet à un utilisateur sans identité de créer son identité
    pub fn create_identity(
        ctx: Context<Initialize>,
        first_name: String,
        last_name: String,
        username: String,
        birth: i64,
        mail: Option<String>
    ) -> Result<()> {
        // TODO
        Ok(())
    }

    /// Permet à un utilisateur de mettre à jour son prénom
    pub fn update_name(ctx: Context<Initialize>, first_name: String) -> Result<()> {
        // TODO
        Ok(())
    }

    /// Permet à un utilisateur de mettre à jour son pseudonyme
    pub fn update_username(ctx: Context<Initialize>, username: String) -> Result<()> {
        // TODO
        Ok(())
    }

    /// Permet à un utilisateur de mettre à jour ou supprimer son mail
    pub fn update_mail(ctx: Context<Initialize>, mail: Option<String>) -> Result<()> {
        // TODO
        Ok(())
    }

		/// Permet à un utilisateur ayant une identité depuis plus de 2 ans
		/// de supprimer son identité 
		pub fn delete_identity(ctx: Context<Initialize>) -> Result<()> {
        // TODO
        Ok(())
    }
}
```

Nos instructions sont assez explicites je pense, pas besoin de revenir dessus.

Il faut maintenant définir nos différents `Accounts` à passer à nos instructions...

---

## Définition des struct Accounts à passer à notre instructions

Bien que cela puisse paraître surprenant, nous auront besoin de définir seulement trois structures `Accounts` différentes !

3 structures pour 5 instructions ? Eh oui !

C’est parce que nos instructions *update* utiliseront toutes la même logique 🙂

Commençons par définir les `Accounts` que notre instruction `create_identity()` à besoin.

Pour rappel, on définit une structure Accounts avec l’attribut `#[derive(Accounts)]`, comme tel:

```rust
#[derive(Accounts)]
pub struct CreateIdentity<'info> {}
```

Puis on ajoute pour chaque nouveau champ, le type d’_account_ qui est attendu.

Il existe [plusieurs types](https://docs.rs/anchor-lang/latest/anchor_lang/accounts/index.html) que vous pouvez renseigner, en voici une liste non-exhaustive:

- Le type `Account<’info, T>`, qui assure que `T` est une donnée dont notre _program_ est propriétaire (Par exemple: notre structure Identity)
- Le type `Signer<’info>`, qui assure que l’_account_ spécifié à bien signer la transaction.
- Le type `Program<’info, T>`, qui assure que l’_account_ spécifié est bien un _program_ `T` où `T` est l’ID du _program_ voulu.
- Le type `UncheckedAccount<’info>`, qui ne procède à aucune vérification sur l’_account_ spécifié.

En plus de ces différents types, il est possible d’utiliser des contraintes (_constraints_) sur nos _accounts_ pour procéder à d’autres vérifications. Il est possible d’ajouter ces contraintes en ajoutant un attribut `#[account()]` au dessus du champ de l’_account_, en ajoutant dans les parenthèses les paramètres voulus.

En voici une list non-exhaustive ([Plus de détails ici](https://docs.rs/anchor-lang/latest/anchor_lang/derive.Accounts.html#normal-constraints)) :

- `#[account(mut)]` rend l’_account_ mutable et permet de modifier son état (Par exemple: lui faire dépenser des SOL).
- `#[account(address = <expr>)]` vérifie que la clé publique de l’_account_ correspond à expr.
- `#[account(init,payer = <target_account>,space = <bytes_size>]` permet d’initialiser l’_account_ spécifié. Un `payer` doit être spécifié pour régler le montant requis lors du stockage des données, ainsi que l’espace maximum que la donnée prend. Nous l’utiliserons juste après pour créer notre identité 🙂

Il en existe encore un autre d’assez important, mais je ne vais pas vous embêter pour le moment avec ça, on y viendra assez vite dans tout les cas ! Voyez ça comme le boss final du développement de `program` sur Solana 😃

Nous avons maintenant toutes les clés en mains pour implémenter notre structure `CreateIdentity`.

Pour résumer, nous avons besoin du compte de l’utilisateur, qui devra signer la transaction pour créer son identité, ainsi que payer la création de ses données. Nous avons aussi besoin du [System Program](https://docs.solana.com/developing/runtime-facilities/programs#system-program) pour créer notre _account_ `Identity` qui stockera l’identité de notre utilisateur.

Voici à quoi ressemble notre Context `CreateIdentity`:

```rust
#[derive(Accounts)]
pub struct CreateIdentity<'info> {
    #[account(mut)]
    pub user: Signer<'info>,
    #[account(init, payer = user, space = Identity::MAX_IDENTITY_SIZE + 8)]
    pub identity: Account<'info, Identity>,
    pub system_program: SystemAccount<'info>
}
```

Remarquez que l’on rend *l’account* de l’utilisateur mutable, requis par le *system program* pour créer l’_account_ _identity_ et payer la *rent* de notre *account identity*.
De plus, vous vous demandez peut-être quel est ce “+ 8” pour le paramètre `space` ?
Je ne vais pas rentrer dans des détails trop technique, mais ce sont 8 bytes requis par anchor lors de la déserialisation de notre *account* Identity.

---

## Définition des autres structures Accounts

Pour les instructions `update_name()`, `update_username()` et `update_mail()`, nous avons besoin du compte de l’utilisateur, de sa signature, de son account _identity_, et c’est tout !

Notez que l’account identity doit être *mutable* vu qu'on va modifier ses données 🙂

```rust
#[derive(Accounts)]
pub struct UpdateIdentity<'info> {
    pub user: Signer<'info>,
    #[account(mut)]
    pub identity: Account<'info, Identity>
}
```

Peut-être que vous avez déjà remarqué que quelque chose n’allait pas... nous y reviendront après ;)
Pour les plus malins, faites comme si tout allait bien et continuons avec l’implémentation de la logique de nos instructions.

---

## Implémentation de nos instructions

### create_identity

Commençons par écrire la première instruction qu’un utilisateur doit appeler, ce qui va initialiser son account `Identity` et stocker son identité.

Pas besoin de gérer l’initialisation de l’account `Identity` car celle-ci est effectuée avec notre contraintes `init` comme vu plus haut (une bonne chose de faite ! 🙂).

Il faut d’abords vérifier que les `String` fournis par l’utilisateur ne dépasses pas la limite fixé, c’est à dire 128 _bytes_ comme définit plus tôt. Si vous êtes familier avec Solidity, anchor propose des macros `require` pour retourner vérifier une condition entre deux variables et retourner une erreur au choix.

❗On pourrait (et est recommandé) faire d’autres vérifications pour optimiser la sécurité de notre _program_, mais je vais faire l’impasse histoire d’alléger pour le moment.

```rust
// Check des infos fournit par l'utilisateur
require_gte!(Identity::MAX_STRING_SIZE, first_name.len());
require_gte!(Identity::MAX_STRING_SIZE, last_name.len());
require_gte!(Identity::MAX_STRING_SIZE, username.len());
if mail.is_some() {
    require_gte!(Identity::MAX_STRING_SIZE, mail.as_ref().unwrap().len());
}
```

Il ne reste plus qu’à enregistrer ces données dans notre tout nouvel account `Identity`.

Pour cela, rien de plus simple, on passe juste les valeurs fournit lors du _call_ de l'instruction par l’utilisateur pour chaque champs de notre account _identity_.

On peux accéder aux données et infos des différents `Accounts` que l’on a passé à notre `Context` avec `ctx.accounts`.

```rust
// Enregistrement des données dans notre account Identity
let user_identity = &mut ctx.accounts.identity;
user_identity.first_name = first_name;
user_identity.last_name = last_name;
user_identity.birth = birth;
user_identity.mail = mail;
user_identity.created = Clock::get().unwrap().unix_timestamp;
```

```rust
/// Permet à un utilisateur sans identité de créer son identité
    pub fn create_identity(
        ctx: Context<CreateIdentity>,
        first_name: String,
        last_name: String,
        username: String,
        birth: i64,
        mail: Option<String>
    ) -> Result<()> {
        // Check des infos fournit par l'utilisateur
        require_gte!(Identity::MAX_STRING_SIZE, first_name.len());
        require_gte!(Identity::MAX_STRING_SIZE, last_name.len());
        require_gte!(Identity::MAX_STRING_SIZE, username.len());
        if mail.is_some() {
            require_gte!(Identity::MAX_STRING_SIZE, mail.as_ref().unwrap().len());
        }

        // Enregistrement des données dans notre account Identity
        let user_identity = &mut ctx.accounts.identity;
        user_identity.first_name = first_name;
        user_identity.last_name = last_name;
        user_identity.birth = birth;
        user_identity.mail = mail;
        user_identity.created = Clock::get().unwrap().unix_timestamp;

        Ok(())
    }
```

Et voila ! Notre première instruction est pleinement implémentée et fonctionnelle.... enfin presque.

Vous vous souvenez du boss final dont j’ai rapidement évoqué plus haut ? Il est venu le temps d'y faire face !

Laissez moi vous parler des **PDA**.

---

## Program Derived Address

### Le problème actuel

Pour commencer, analysons le problème qui se pose actuellement sur notre _program_ d’identité.

Je vous rappelle que chaque utilisateur a sa propre identité, c’est à dire que pour chaque utilisateur/clé publique, un _account_ `Identity` doit être créé et exister.

Premièrement, cela veut dire qu’un utilisateur doit d’abord générer une nouvelle paire de clé publique/privée qui sera son _account_ `Identity` si il souhaite une identité, ce qui n’est pas très pratique. Cela permettrait en plus d’avoir un nombre infini d’identité pour une seule clé publique.
Ensuite, même si les utilisateurs était d’accord avec ce système, cela comporte un énorme problème de sécurité. Regardons sur nos `Accounts` que l’on passe pour nos instructions _update_:

```rust
#[derive(Accounts)]
pub struct UpdateIdentity<'info> {
    pub user: Signer<'info>,
    #[account(mut)]
    pub identity: Account<'info, Identity>
}
```

A quoi sert `user` ? Eh bien... pour le moment, à rien.
En effet, peu importe qui signe la transaction, et peu importe la clé publique de l’_account_ _identity_, les modifications seront pris en compte pour l’_account_ _identity_....

Le problème ici est que nous n’avons aucun lien entre l’utilisateur signant la transaction et l’_account identity_ fourni, et qu’il est impossible de vérifier la propriété et l’unicité d’une identité par rapport à la clé publique d’un utilisateur.

Bon, rassurez-vous, si je vous parle de tout ça, c’est que le PDA résout tout ces problèmes 🙂

### Explication des PDA

Avant toute chose, le PDA est l'un des principes les plus fourbes, mais aussi des plus important pour un développeur Solana, ne vous découragez pas maintenant !

Les PDA, pour “_Program Derived Address_” où bien “Adresse dérivée de programme”, sont des adresses générées à partir de l’ID d’un _program_ et de plusieurs _seeds_.

Une PDA a une certaine particularité, elle ne doit pas appartenir à la courbe *ed25519 !*
Ce qui veut dire qu’une PDA a la forme d’une clé publique, **MAIS N’A PAS** de clé privée associée. Il est donc impossible pour un utilisateur de générer une signature valide pour un _account_ avec une PDA en tant que clé publique !

Le PDA est un remplaçant direct au _Mapping_ qu'on pourrait connaitre sur Solidity pour associer une adresse à une donnée. (Pour ceux qui se posent la question, `HashMap` de Rust n’est pas fonctionnelle dans un _program_ sur Solana “pour le moment”).

Maintenant, penchons nous sur la fonction suivante:

```javascript
findProgramDerivedAddress(programId, seeds)
```

Cette fonction retourne l’adresse trouvée à partir de l’ID du _program_ fournit ainsi qu’une seed fournit. Le problème étant que cette fonction a une chance de réussite d’environ 50%.

Souvenez-vous qu’une PDA valide ne doit pas appartenir à la courbe *ed25519*, de ce fait, nous devons être certain que notre fonction renvoie une adresse valide.

Pour ce faire, il faut ajouter un troisième argument, qu'on appelle le “_bump_”. Ce _bump_ est un entier qui sera incrémenté à chaque fois qu’une adresse non-valide est retournée. La fonction va alors se répéter en incrémentant le _bump_ jusqu’à trouver une adresse ne se trouvant pas sur la courbe.

```javascript
findProgramDerivedAddress(programId, seeds, bump)
```

Grâce au PDA, nous pouvons désormais créer un _account_ `Identity` unique pour chaque utilisateur sans devoir stocker quoi que ce soit car cette adresse est calculable directement par notre _program_ ou nos utilisateurs !

Nous pourrons aussi définir des contraintes pour faire en sorte que l’utilisateur qui signe la transaction ne puisse accéder qu’à son propre _account_ `Identity` et la modifier en conséquence.

---

## Implémentation du PDA

### CreateIdentity

Cette implémentation se fait au niveau de nos structures `Accounts`.

Regardons du coté de nos `Accounts` que l’on passe pour notre instruction de création d’identité:

```rust
#[derive(Accounts)]
pub struct CreateIdentity<'info> {
    #[account(mut)]
    pub user: Signer<'info>,
    #[account(
        init,
        payer = user, 
        space = Identity::MAX_IDENTITY_SIZE + 8,
        // PDA à implémenter
    )]
    pub identity: Account<'info, Identity>,
    pub system_program: SystemAccount<'info>
}
```

Bien que la notion de PDA puisse être complexe à comprendre, l’implémentation de celle-ci se fait en quelques lignes !

Nous devons ajouter une contrainte `seeds` lors de l’initialisation de notre _account_ _identity_ qui permet de calculer la PDA à partir de la _seeds_ fournit, et de refuser toute autre adresse passée si l’adresse n’est pas la PDA calculée.

Nous devons maintenant savoir quelle _seed_ utiliser. En règle générale, notre _seed_ sera basée sur au moins trois paramètres:

- Une chaine de caractères pour pouvoir différencier la génération d’une PDA d’un certain type d’_account_ à d’autres.
- La clé publique de notre utilisateur signant la transaction. C’est ce qui permet de faire le lien entre son identité et sa clé publique ! En effet, chaque clé publique générera une PDA différente 🙂.
- Le _bump_, essentiel pour assurer notre _program_ de trouver une PDA avec nos deux premières _seeds_ fournies. Notre _program_ utilisera le premier _bump_ valide, aussi appelé “bump canonique” (*_canonical bump_*)

Retranscrit au niveau de notre code, voici ce que l’on obtient:

```rust
seeds = [b"Identity", user.key().as_ref()], bump
```

Il faut ensuite sauvegarder le bump trouvé par notre _program_ pour s’assurer que l’adresse générée plus tard pour accéder à notre _account_ `Identity` sera toujours la même.

```rust
/// Définit la structure d'une identité d'un utilisateur
#[account]
pub struct Identity {
    pub first_name: String, // 128 + 4 = 132
    pub last_name: String,  // 128 + 4 = 132
    pub username: String,   // 128 + 4 = 132
    pub birth: i64, // 8
    pub mail: Option<String>, // 128 + 1 = 129
    pub created: i64, // 8
    pub bump: u8 // 1
}
```

！N’oubliez pas de rajouter l’espace que prends le _bump_ dans notre structure `Identity` sur la constante `MAX_IDENTITY_SIZE`.

Il ne reste plus qu’à stocker le _bump_ trouvé par notre _program_ lors de la création de notre identité en l’implémentant dans notre instruction. Les _bumps_ calculés par notre _program_ sont accessibles par `ctx.bumps.get(<account>)`.

```rust
user_identity.bump = *ctx.bumps.get("identity").unwrap();
```

### UpdateIdentity

L’implémentation est presque identique pour nos `Accounts` d’_update_.
Il faut vérifier que l’adresse passée est bien la PDA calculée pour l’utilisateur qui signe la transaction.

```rust
#[account(
    mut,
    seeds = [b"Identity", user.key().as_ref()], bump = identity.bump
)]
pub identity: Account<'info, Identity>
```

### CloseIdentity

```rust
#[account(
    mut,
    close = user,
    seeds = [b"Identity", user.key().as_ref()], bump = identity.bump
)]
pub identity: Account<'info, Identity>
```

Et voila ! Notre logique de PDA est bien implémentée et chacun de nos utilisateurs ne peut gérer qu’une seule identité et seulement la leur 🙂

--

## Implémentation de nos instructions Part. 2

Implémentons maintenant la logique de nos instructions de modifications.
Cela sera le même modèle pour nos trois instructions:

1. Vérification de la donnée
2. Stockage de la donnée

### update_name()

```rust
// Permet à un utilisateur de mettre à jour son prénom
pub fn update_name(ctx: Context<UpdateIdentity>, first_name: String) -> Result<()> {
    require_gte!(Identity::MAX_STRING_SIZE, first_name.len());

    ctx.accounts.identity.first_name = first_name;

    Ok(())
}
```

### update_username()

```rust
/// Permet à un utilisateur de mettre à jour son pseudonyme
pub fn update_username(ctx: Context<UpdateIdentity>, username: String) -> Result<()> {
    require_gte!(Identity::MAX_STRING_SIZE, username.len());

    ctx.accounts.identity.username = username;
    
    Ok(())
}
```

### update_mail()

```rust
/// Permet à un utilisateur de mettre à jour ou supprimer son mail
pub fn update_mail(ctx: Context<UpdateIdentity>, mail: Option<String>) -> Result<()> {
    if mail.is_some() {
        require_gte!(Identity::MAX_STRING_SIZE, mail.as_ref().unwrap().len());
    }

    ctx.accounts.identity.mail = mail;

    Ok(())
}
```

### delete_identity()

L’étape finale consiste à implémenter l’instruction qui permettra à un utilisateur de supprimer son identité si celle-ci existe depuis plus de deux ans.

La fermeture de l’_account_ étant déjà gérée par la contrainte `close`, il s’agira ici seulement de vérifier que l’identité existe depuis au moins 2 ans, ce sans quoi la transaction sera *revert* et donc l’_account_ ne sera pas fermé.
```rust
/// Permet à un utilisateur ayant une identité depuis plus de 2 ans
/// de supprimer son identité 
pub fn delete_identity(ctx: Context<CloseIdentity>) -> Result<()> {
    let now = Clock::get().unwrap().unix_timestamp;
    let created = ctx.accounts.identity.created;
    let since = now - created;

    require_gt!(since, CAN_DELETE_AFTER);

    Ok(())
}
```

---

## Définition et émission d'un événement (_Event_)

Les _Events_ sont des éléments importants à mettre en place pour garder un historique efficace de certaines données et faciliter la communication avec nos applications off-chain.
Celles-ci pourront souscrire à l'_event_ lié à un certain contexte de notre _program_, et exécuter une ou des actions en conséquence à chaque nouvel événement émit.

Nous allons mettre en place un _event_ qui sera émit à chaque création d'une nouvelle identité.
Un _event_ se définit par une structure comportant l'attribut `#[event]` proposé par anchor. Cette structure peut accueillir divers champs qui définiront les données que notre _event_ contiendra.

Dans notre cas, nous souhaitons que notre _event_ contienne la clé publique de l'utilisateur ayant créé son identité, son pseudonyme ainsi que la date et l'heure de la création de l'identité.

```rust
#[event]
pub struct IdentityCreated {
    pub pubkey: Pubkey,
    pub username: String,
    pub timestamp: i64,
}
```

Rien de sorcier ici, il ne nous reste plus qu'à émettre notre événement à la fin de notre instruction.
La macro `emit!()` fournit par anchor nous permet d'émettre une structure comportant l'attribut `#[event]` comme suit:
```rust
// Emet un `Event` signifiant qu'une nouvelle identité est crée
        emit!(event::IdentityCreated {
            pubkey: ctx.accounts.user.key(),
            username,
            timestamp: ctx.accounts.identity.created
        });
```

---

## Définition de nos erreurs personnalisées

Pour finaliser notre _program_, nous pouvons définir et implémenter des erreurs personnalisées avec des messages plus explicites pour nos utilisateurs suivant les différents contextes.

Anchor propose un attribut `#[error_codes]` qui permet d’implémenter le type `Error` de anchor à une énumération d’erreurs personnalisées.

Encore une fois, je vais définir les erreurs dans un fichier différent que j’appellerai `error.rs`

```rust
use anchor_lang::error_code;

#[error_code]
pub enum IdentityError {
    StringTooLarge,
    TimeNotPassed
}
```

Rien de bien compliqué ici 🙂

Pour définir un message personnalisé sur une erreur, nous pouvons utiliser l’attribut `#[msg]`:

```rust
#[msg("Specified string is higher than the expected maximum space")]
StringTooLarge,
#[msg("2 year is needed since the creation of the identity to be closed")]
TimeNotPassed
```

Il ne reste plus qu’à implémenter nos erreurs personnalisées dans nos instructions !

```rust
require_gt!(since, CAN_DELETE_AFTER, IdentityError::TimeNotPassed);
```

Et voila ! Notre _program_ est désormais terminé, bien qu’encore améliorable, mais ce n’est pas le but de cet article 🙂
```rust
mod identites;
mod error;

use anchor_lang::prelude::*;
use identites::Identity;
use error::IdentityError;

declare_id!("GxyJLSDuC7BkeorKoMg87uhaXvuaUxDjDKT7iQWCbxXJ");

#[program]
pub mod identity {
    use super::*;

    pub const CAN_DELETE_AFTER: i64 = 31556926 * 2;
    
    /// Permet à un utilisateur sans identité de créer son identité
    pub fn create_identity(
        ctx: Context<CreateIdentity>,
        first_name: String,
        last_name: String,
        username: String,
        birth: i64,
        mail: Option<String>
    ) -> Result<()> {
        // Check des infos fournit par l'utilisateur
        require_gte!(Identity::MAX_STRING_SIZE, first_name.len(), IdentityError::StringTooLarge);
        require_gte!(Identity::MAX_STRING_SIZE, last_name.len(), IdentityError::StringTooLarge);
        require_gte!(Identity::MAX_STRING_SIZE, username.len(), IdentityError::StringTooLarge);
        if mail.is_some() {
            require_gte!(Identity::MAX_STRING_SIZE, mail.as_ref().unwrap().len(), IdentityError::StringTooLarge);
        }

        // Enregistrement des données dans notre account Identity
        let user_identity = &mut ctx.accounts.identity;
        user_identity.first_name = first_name;
        user_identity.last_name = last_name;
        user_identity.birth = birth;
        user_identity.mail = mail;
        user_identity.created = Clock::get().unwrap().unix_timestamp;
        user_identity.bump = *ctx.bumps.get("identity").unwrap();

        Ok(())
    }

    /// Permet à un utilisateur de mettre à jour son prénom
    pub fn update_name(ctx: Context<UpdateIdentity>, first_name: String) -> Result<()> {
        require_gte!(Identity::MAX_STRING_SIZE, first_name.len(), IdentityError::StringTooLarge);

        ctx.accounts.identity.first_name = first_name;

        Ok(())
    }

    /// Permet à un utilisateur de mettre à jour son pseudonyme
    pub fn update_username(ctx: Context<UpdateIdentity>, username: String) -> Result<()> {
        require_gte!(Identity::MAX_STRING_SIZE, username.len(), IdentityError::StringTooLarge);

        ctx.accounts.identity.username = username;

        Ok(())
    }

    /// Permet à un utilisateur de mettre à jour ou supprimer son mail
    pub fn update_mail(ctx: Context<UpdateIdentity>, mail: Option<String>) -> Result<()> {
        if mail.is_some() {
            require_gte!(Identity::MAX_STRING_SIZE, mail.as_ref().unwrap().len(), IdentityError::StringTooLarge);
        }

        ctx.accounts.identity.mail = mail;

        Ok(())
    }

    /// Permet à un utilisateur ayant une identité depuis plus de 2 ans
	/// de supprimer son identité 
    pub fn delete_identity(ctx: Context<CloseIdentity>) -> Result<()> {
        let now = Clock::get().unwrap().unix_timestamp;
        let created = ctx.accounts.identity.created;
        let since = now - created;

        require_gt!(since, CAN_DELETE_AFTER, IdentityError::TimeNotPassed);

        Ok(())
    }
}

#[derive(Accounts)]
pub struct CreateIdentity<'info> {
    #[account(mut)]
    pub user: Signer<'info>,
    #[account(
        init,
        payer = user, 
        space = Identity::MAX_IDENTITY_SIZE + 8,
        seeds = [b"Identity", user.key().as_ref()], bump
    )]
    pub identity: Account<'info, Identity>,
    pub system_program: SystemAccount<'info>
}

#[derive(Accounts)]
pub struct UpdateIdentity<'info> {
    pub user: Signer<'info>,
    #[account(
        mut,
        seeds = [b"Identity", user.key().as_ref()], bump = identity.bump
    )]
    pub identity: Account<'info, Identity>
}

#[derive(Accounts)]
pub struct CloseIdentity<'info> {
    pub user: Signer<'info>,
    #[account(
        mut,
        close = user,
        seeds = [b"Identity", user.key().as_ref()], bump = identity.bump
    )]
    pub identity: Account<'info, Identity>
}
```
```rust
use anchor_lang::prelude::*;

/// Définit la structure d'une identité d'un utilisateur
#[account]
pub struct Identity {
    pub first_name: String, // 128 + 4 = 132
    pub last_name: String,  // 128 + 4 = 132
    pub username: String,   // 128 + 4 = 132
    pub birth: i64, // 8
    pub mail: Option<String>, // 128 + 1 = 129
    pub created: i64, // 8
    pub bump: u8 // 1
}

impl Identity {
    pub const MAX_STRING_SIZE: usize = 128;
    pub const MAX_IDENTITY_SIZE: usize = 132 + 132 + 132 + 8 + 129 + 8 + 1;
}
```
```rust
use anchor_lang::error_code;

#[error_code]
pub enum IdentityError {
    #[msg("Specified string is higher than the expected maximum space")]
    StringTooLarge,
    #[msg("2 year is needed since the creation of the identity to be closed")]
    TimeNotPassed
}
```

---

## Conclusion

Vous devriez désormais avoir les clés en mains pour faire vos propres _programs_ Solana !

Une version plus "_rustic_" du projet est disponible [sur mon github](https://github.com/SailorSnoW/Identity).

Dans une prochaine partie, j’expliquerai comment tester les _programs_ que vous produisez, toujours via anchor, avec Typescript + Chai.

J’évoquerai aussi prochainement les tokens sur Solana (SPL), les Cross-Program Invocations (CPI) ou divers articles sur d'autres technologies web3.

Si vous aimez mon contenu et/ou que celui-ci vous aide en tant que développeur, vous êtes le bienvenue sur mes différents réseaux 😊

[LinkedIn](https://www.linkedin.com/in/llagoutte/)
[Twitter](https://www.twitter.com/0xSnoW)
[Instagram](https://www.instagram.com/sailor_snxw)