Le Wrapper RMM est un contrat intelligent qui sert d'interface entre les RealTokens et le protocole RMM (RealToken Market Maker). Il permet aux utilisateurs de déposer leurs RealTokens et de recevoir en échange des RTW (RealToken Wrapped) qui sont utilisés dans le RMM comme preuve de dépôt pour emprunter.
Le Wrapper joue un rôle crucial dans l'écosystème RMM en :
adresse du wrapper : 0x10497611Ee6524D75FC45E3739F472F83e282AD5
Ces modifications font suite à la proposition de gouvernance RIP00008 qui a été acceptée par la DAO en date du 2024-12-07 21h19 UTC.
RIP00008 - Transfer RealToken Wrapper RMMv3 execution functions to the DAO
Les principales modifications apportées au Wrapper ont été effectuées pour garantir la conformité légale et les opérations courantes liées à la vie d'un RealToken.
Dans certains cas, l'émetteur peux être amené à devoir détruire des Realtokens :
ce qui pourrait rendre le RMM dysfonctionnel, pour les Realtoken correspondants, car il n'y aurait plus de collatéral pour couvrir les dettes contractés.
Dans l'objectif de pouvoir répondre à ces obligations et assurer la continuité et le bon fonctionnement du RMM, de nouvelles fonctionnalités ont été ajoutées au Wrapper pour rembourser partiellement ou intégralement une dette liée à un Realtoken sur un compte et d'extraire de force le Realtoken concerné des comptes concernés.
Ce processus est supervisé par le vote de la DAO pour s'assurer qu'il n'y a pas d'abus.
Il garantie que le montant du par la détokenisation soit correctement perçu par le propriétaire, que le RMM pourra continuer de fonctionner et de respecter les obligations légales.
Dans l'objectif que conserver un bon fonctionnement du RMM, les modifications apportées au Wrapper ont été effectuées pour permettre de récupérer les RealTokens liés à une offre en cas de décès du propriétaire, de vente de la maison liée au Realtoken, du remboursement d'un partie ou l'intégralité de la dette liée au Realtoken, de la détokenisation ou d'autres événements rendant l'actif sous-jacent inéligible au dépôt dans le RMM.
Pour récupérer ou forcer le retrait des RealTokens du protocole RMMv3, deux fonctions sont nécessaires :
repayForRecover
: Cette fonction gère le remboursement partiel ou intégral de la dette qui peut être active sur le compte d'un utilisateur du RMM (dans les cas comme le décès, détokenisation, autres...). Une fois exécutée, la seconde fonction peut être appelée en toute sécurité pour extraire les RealTokens du RMM.recoverByGovernance
: Cette fonction récupère les RealTokens, soit en les retirant vers une adresse spécifiée (adresse de service pour burn), soit en déplaçant les aTokens/Realtokens vers l'adresse spécifiée (nouvelle adresse de l'utilisateur).Ces deux fonctions ne peuvent être appelées que par la gouvernance de l'écosystème (REG DAO). Une proposition doit être soumise dans le processus actif de la DAO et inclure l'appel à ces deux fonctions ainsi que les approuves utiles à son exécution, correctement configurées pour chaque adresse nécessitant une récupération (batch d'adresse et de tokens).
Ces fonctions sont nécessaires dans les cas suivants :
Contrôle d'accès : Ces deux fonctions sont limitées aux contrats de gouvernance, seule une proposition approuvée par la communauté de gouvernance de l'écosystème RealToken (REG) peut les activer, cette mesure permet d'avoir une surveillance de l'usage d'une fonctionnalité qui peut être dangereuse, et de donner une transparence totale.
Ces modifications donnent un droit total sur les tokens déposés dans le RMM, il est donc vital que toute soumission d'activation de ces fonctionnalités soit minutieusement analysée par la DAO pour s'assurer qu'il n'y a pas d'abus.
En principe, seul RealT ou un partenaire de la DAO approuvé qui emmétrait des tokens devrait initialiser de telle proposition.
La DAO doit donc veiller à ce que ces fonctionnalités soient activées uniquement dans des cas le demandant expressément pour respecter une réglementation ou et garantir le bon fonctionnement du RMM.
L'usage abusif de ces fonctionnalités pourrait conduire à des vols de tokens, des remboursements de dettes abusifs, des emprunts non légitimes, portant atteinte à la sécurité du RMM et à ses utilisateurs.
Les principaux enjeux liés au Wrapper sont :
Il est crucial de comprendre que le Wrapper est un composant central du RMM et que toute modification, usage de ses fonctionnalités doit être soigneusement évaluée.
Le Wrapper RMM v3 est un contrat central dans l'extension des capacités du RMM et il conserve la majeur partie de la valeur mis en garantie des dettes du RMM.
Chaque modification ou usage de ces fonctionnalités doit être analysé minutieusement pour s'assurer qu'il n'y a pas d'abus, ou d'erreur qui pourrait endommager le RMM.
Les principales considérations sont :
repayWallet
(address) : L'adresse détenant les RealTokens dans le RMMv3 et dont les tokens seront extrait du RMM sur lesquels une dette est active.refundWallet
(address) : L'adresse qui recevra tout excédent de paiement après le remboursement de la dette.percent
(uint256) : Le pourcentage (échelle 100% = 10000) de chaque token déposé qui sera extrait du RMM.recoverAssets
(address[]) : Un tableau d'adresses de tokens représentant les RealTokens qui seront extrait du RMMv3.debtAssets
(address[]) : Un tableau d'adresses de tokens représentant les actifs de dette à rembourser.payer
(address) : L'adresse responsable du paiement de la dette,transferFrom
de chaque token debtAssets au bénéfice du Wrapper.Note : La valeur des actifs quels qu'ils soient dans le RMM est calculé en dollard USD, ainsi il est possible qu'un USDC/XDAI ou autre ne soit pas exactement a 1$USD, il est donc important de bien calculer la balance du payer
avec une marge de sécurité qui peux être raisonnablement de 5/100k.
Note : si la fonciton repay est utilisée pour un seul utilisateur avec une ou plusieurs adresse, le refundWallet
peux etre directement une adresse de l'utilisateur.
Dans le cas d'usage en batch pour plusieurs utilisateur, le refundWallet
doit etre une adresse de service qui permettera de distribuer les montants correctes à chaque utilisateur pour qui le montant de remboursement n'a pas été totalement consommé par le remboursement de la dette.
payer
a une balance des tokens debtAssets supérieur ou égale à la dette à rembourser et avoir une approval de la fonction transferFrom
de chaque token debtAssets au bénéfice du Wrapper.L'activation de cette fonction permet l'extraction des RealTokens d'un compte utilisateur, toutefois pour que la transaction n'échoue pas, il est nécessaire que le nombre de realtoken à extraire soit permis par le niveau de collatéral du compte.
Il est donc important de bien calculer la valeur des tokens à extraire et son impact sur le niveau de collatéralisation, si il est insufisant pour executer avec succès la fonction, il est impératif d'executer la fonction repayForRecover
avant de pouvoir executer recoverByGovernance
.
Note : Il est impétatif d'executer la fonction repayForRecover
et recoverByGovernance
dans la meme transaction, afin de prévenir une attaque de double dépenses dans la quelle l'utilisateur réussirait à emprunter après l'execution de la fonction repayForRecover
(qui rembouree la dette) et l'execution de la fonction recoverByGovernance
(qui extrait les RealTokens), cette dernière échouerait.
oldWallet
(address) : L'adresse détenant les RealTokens dans le RMMv3 et dont les tokens seront extrait du RMMnewWallet
(address) : L'adresse qui recevra les Realtokens ou RTW associés.percent
(uint256) : Le pourcentage (échelle 100% = 10000) de chaque realtoken déposer qui sera extrait du RMM.recoverAssets
(address[]) : Un tableau d'adresses de tokens représentant les RealTokens qui seront extraits du RMMv3.withdraw
(bool) : valeur booléen qui détermine si les tokens seront retirés du RMM ou déplacés vers la nouvelle adresse. True : les Realtokens seront retirés du RMM; False : les RTW seront déplacés vers la nouvelle adresse.Note : Dans une proposal qui est executée pour plusieurs utilisateurs, il faudra autant d'appel à la fonction qu'il y a d'utilisateurs pour lesquels il faut extraire les RealTokens.
Les fonctions repayForRecover
et recoverByGovernance
ne gèrent pas le Facteur de Santé (HF). Par conséquent, il faut s'assurer au préalable que le HF permet le retrait, sans quoi la proposal dans son intégralité échouera.
La fonction recoverByGovernance
ne vérifie pas les dettes existantes. Cela doit être anticipé dans la proposition. La fonction repayForRecover
doit être exécutée au préalable si nécessaire pour éviter un échec.
Les adresses qui à un moment récéptionnent des RealTokens, doivent impérativement respecter les règles de la compliance registry de ces tokens, sans quoi la proposal dans son intégralité échouera.
Les approbations, et solde de balance pour le paiement des dettes doivent impérativement inclure un marge minimum de 5/100k, sans quoi la proposal dans son intégralité échouera si la valeur du token de dette à rembourser à une valeur inférieur a 1$USD.
Liste des modifications dans la mise à jour de RealTokenWrapper :
Suppression de la vérification _isRealTokenWhitelisted pour la fonction withdraw (ligne 171) : puisqu'elle vérifie déjà le solde de tokens de l'utilisateur dans RealTokenWrapper. S'il y a un solde > 0, cela signifie que le RealToken était whitelisté. Il devrait permettre le retrait et ne pas permettre l'approvisionnement si le RealToken n'est plus whitelisté maintenant.
if (!_isRealTokenWhitelisted[asset]) revert WrapperErrors.TokenNotWhitelisted(asset);
https://gnosisscan.io/address/0x12a000a8A2Cd339D85119C346142Adb444bc5ce5#code#F1#L168
Utilisation du tableau de tokens en cache de l'utilisateur pour l'optimisation du gas (_tokenListOfUser au lieu de _realTokenList)
Changement de la lecture directe depuis le mapping de stockage _realTokenList
uint256 length = _realTokenList.length;
uint256 userIndex = 0;
https://gnosisscan.io/address/0x12a000a8A2Cd339D85119C346142Adb444bc5ce5#code#F1#L479
vers la copie de _tokenListOfUser[user] en mémoire (cette liste est individuelle pour chaque utilisateur) (lignes 484-485)
// Cache user token list for gas optimization
address[] memory userTokenList = _tokenListOfUser[user];
uint256 length = userTokenList.length;
https://gnosisscan.io/address/0xd32616a28fcfb8fab292c3819e87821733f74a83#code#F1#L481
Suppression de tous les anciens recoverByGovernance et ajout des 2 nouvelles fonctions repayForRecover/recoverByGovernance (lignes 643 à 870)
https://gnosisscan.io/address/0xd32616a28fcfb8fab292c3819e87821733f74a83#code#F1#L643
https://gnosisscan.io/address/0xd32616a28fcfb8fab292c3819e87821733f74a83#code#F1#L764
IMPORTANT : la validation d'une proposal qui executera les fonctions
repayForRecover
etrecoverByGovernance
doit être faite avec une grande attention, car elle peux etre très dangereuse si elle n'est pas correctement configurée ou utilisée à des fins malveillantes.
Objectif : Correction d'un bug lié à l'optimisation du gas pour la liste des tokens déposés.
Problème rencontré : Lorsqu'un utilisateur effectue une liquidation et récupère les aTokens, si ces derniers ne sont pas dans la liste des dépôts, les fonctions utilisant cette liste ne trouvent pas ces aTokens.
Les valeurs sont correctement enregistrées dans le Wrapper, les RTW sont bien créés et déposés, mais le RMM et le Wrapper ne trouvent pas les valeurs car les fonctions getAllTokenBalancesOfUser
et getUserIndex
utilisent la variable _tokenListOfUser
qui ne contient pas toutes les informations.
Solution : Correction de la fonction de liquidation pour vérifier si les tokens récupérés sont dans _tokenListOfUser
. Si ce n'est pas le cas, ils sont ajoutés, ce qui permet au Wrapper et au RMM de récupérer toutes les informations correctement.
Nouvelle implémentation déployée : https://gnosisscan.io/address/0xc7ca0b893c22f99bb99dfc9dafdb6a83e0e7a946#code
Modifications :
Code ajouté lignes 312 à 317
https://gnosisscan.io/address/0xc7ca0b893c22f99bb99dfc9dafdb6a83e0e7a946#code#F1#L312
Code ajouté lignes 357 à 362
https://gnosisscan.io/address/0xc7ca0b893c22f99bb99dfc9dafdb6a83e0e7a946#code#F1#L357
Réalisé par la société ADBK : https://github.com/abdk-consulting/audits/tree/main/realt