Soliditips est une série d’articles qui viennent mettre en lumière certains fonctionnements particuliers de Solidity, le langage des smart contracts d’Ethereum. 

Pour ce deuxième article, nous verrons comment une optimisation technique peut vous faire gagner de l’argent. 

En principe, quand un développeur parle d’optimiser son code, il va chercher à en améliorer les performances, la vitesse d’exécution, la lisibilité, la maintenabilité … 

Sur Ethereum, toute transaction doit s’acquitter de frais pour être validée. C’est d’autant plus vrai pour un déploiement de smart contract. Dans ce cas, l’optimisation peut également être financière. Ni compte en suisse, ni société écran dans un paradis fiscal, juste le bon ordre… 

Étudions les deux codes ci-dessous : 

Code N°1 :  

contract Storage { 

	uint128 storage1;
	uint128 storage2;
	uint256 storage3;

	constructor() public { 
		storage1 = 8; 
		storage2 = 8; 
		storage3 = 8; 
	}
}

Code N°2 : 

contract Storage { 

	uint128 storage1;
	uint256 storage3;
	uint128 storage2;

	constructor() public { 
		storage1 = 8; 
		storage2 = 8; 
		storage3 = 8; 
	}
} 

Chacun de ces contrats déclare trois variables, deux entiers non signés de 128 bits et un entier non signé de 256 bits et les initialise avec la même valeur dans le constructeur. 

Ils font la même chose. 

Et pourtant, le premier va nécessiter 111 572 gaz, et le second, 130 722 gaz. A l’heure où cet article est écrit, avec 1 ETH = 249,10 EUR, et le gaz à 1 gwei, le premier contrat nous coûte 0,028€ à déployer alors que le second nous coûte 0,033€, soit presque 20% de plus.

Pourquoi c’est différent alors que c’est pareil ? 

Si vous comparez les 2 codes, vous verrez que la différence porte sur l’ordre de déclaration des variables. Uint128, uint128, uint256 pour le premier et uint128, uint256, uint128 pour le second. Les 2 dernières déclarations sont permutées. 

Que se passe-t-il ? 

L’Ethereum Virtual Machine attribue une zone de stockage pour chaque contrat. Cette zone est découpée en slots de 256 bits. Lors de la création d’un contrat, ses variables vont se voir attribuer un slot chacun, dans l’ordre dans lequel ils sont déclarés

Si un slot n’est pas rempli et que le champ suivant peut se glisser dans la place restante, il le fera. 

Le premier code permet donc de placer deux entiers de 128 bits dans un seul et même slot. L’entier de 256 bits prendra le second slot. Ce code a besoin de 2 slots seulement. 

slot 1uint 128uint 128
slot 2uint 256

Le deuxième code placera le premier uint128 dans un slot. L’uint256 qui vient ensuite ne peut pas utiliser l’espace restant, il est trop grand. Il prendra le slot entier suivant. Le dernier uint128 devra également prendre un slot à la suite. Ce code a donc besoin de 3 slots. Et il n’exploitera pas l’espace de façon optimale puisqu’il reste des espaces inutilisés.

slot 1uint 128128 bits inutilisés
slot 2uint 256
slot 3uint 128128 bits inutilisés

Chaque slot utilisé se paye. Le second code coûte donc plus cher que le premier à déployer. 

Ce qui est illustré ici avec des entiers dans un petit contrat simple est bien entendu vrai pour tous types de variables dans n’importe quel contrat. 

Alors la prochaine fois que vous déclarerez vos variables, pensez à vos slots !

Edit : quelques temps après la rédaction initiale de cette article, le cours de l’Ether a augmenté, et le prix des frais des transaction a bondi suite à l’engorgement du réseau Ethereum. Le prix du gaz est passé à 97 gwei à l’heure de cette mise à jour.

Le premier contrat est passé à 0.010822484 ETH soit 3,16€ à déployer contre 0.012680034 ETH soit 3,70€ pour le second. Il y a donc tout intérêt à optimiser au mieux les déclarations de variables pour subir le moins possible les fluctuations des coûts de transaction.