ethereum_smartcontract_intro
311 строк · 13.0 Кб
1// SPDX-License-Identifier: UNLICENSED
2
3// Ограничение на версию компилятора
4pragma solidity >=0.8;
5
6// Делаем видимыми в текущем файле конкретные сущности из другого файла.
7import {DebugPrintContract, DebugStorageContract} from "./Debug.sol";
8
9// Библиотека для отладочного вывода в тестовой сети.
10import "hardhat/console.sol";
11
12// Константа времени компиляции.
13string constant DEMO_CONTRACT_NAME = "DemoContract";
14
15
16// Библиотека - это контракт без состояния (все вызовы идут через delegatecall),
17// для которого предоставляется упрощённый синтаксис вызова из других контрактов.
18// Remix может автоматически деплоить контракт библиотеки (отдельной транзакцией)
19// и подставлять адрес этого контракта в места вызова функций библиотеки.
20// Если библиотека уже задеплоина, то можно указать Remix'у её адрес.
21library DemoLibrary {
22
23function DLadd (uint a, uint b) public pure returns (uint) {
24return a + b;
25}
26}
27
28
29// Будет использоваться в качестве родительского контракта.
30// Но может быть и задеплоен сам, т.к. не является абстрактным.
31contract BaseContract {
32
33// Для существующего типа можно завести синоним.
34type ValueType is uint;
35
36// константу (const) надо инициализировать во время компиляции
37//uint constant baseVar;
38// неизменяемую переменную можно инициализировать в конструкторе контракта
39ValueType internal immutable _value;
40
41constructor (uint value) {
42
43// аналог asssert в других языках - используется для проверки инвариантов
44assert (address(this).balance == 0);
45
46// конвертация из значения исходного типа
47_value = ValueType.wrap(value);
48}
49
50// Возможна перегрузка функций по аргументам.
51function f () public pure {
52
53}
54
55function f (uint a) public pure {
56
57}
58
59// Можно определять собственные модификаторы функций.
60// Похожи на декораторы в Python.
61modifier NotNull (uint a) {
62
63// Проверка условия.
64// Если условие ложно, выполняется инструкция revert с указанной строкой.
65require(a != 0, "is null");
66
67_; // вызов исходной функции
68}
69}
70
71
72// Контракт наследуется от двух контрактов.
73contract DemoContract is BaseContract, DebugPrintContract {
74
75// В этом контракте для типа uint будут доступны функции библиотеки
76// с первым аргументом этого типа.
77using DemoLibrary for uint;
78
79using DebugStorageContract for *;
80
81// Константа должна быть проинициализирована во время компиляции.
82string constant contractName = DEMO_CONTRACT_NAME;
83
84// целочисленные беззнаковые переменные
85uint8 private ui8 = 0x8;
86uint16 private ui16 = 0x16;
87uint24 private ui24 = 0x24;
88// ...
89uint256 private ui256 = 0x256;
90
91// целочисленные знаковые переменные
92int8 private i8 = 0x18;
93//...
94int256 private i256 = 0x1256;
95
96// массивы байт фиксированной длины
97bytes1 private b1 = 0x01;
98bytes2 private b2 = 0x0202;
99bytes3 private b3 = 0x030303;
100bytes4 private b4 = 0x04040404;
101//...
102bytes32 private b32 = hex"0505050505050505050505050505050505050505050505050505050505050505";
103
104// массив фиксированной длины
105uint64[2] private uia2 = [0x010101010101, 0x02020202];
106
107// массив произвольной длины
108// В его слоте располагается текущая длина массива.
109// i-ый элемент располагается в слоте (keccak256(uida.slot) + i * elementSize)
110uint[] private uida;
111
112// массив байт произвольной длины
113bytes ba = hex"0102030405";
114
115// Для отображения отводится пустой слот.
116// Элемент с ключом key располагается в слоте keccak256(key, uumap.slot)
117mapping (uint => uint) uumap;
118
119struct Entry {
120uint256 a;
121uint8 b;
122}
123
124Entry[5] sea5;
125
126// Конструктор может быть только один.
127// Можно передавать аргументы в конструкторы базовых классов.
128constructor (uint a) payable BaseContract(a) {
129
130DebugPrint ("a = ", a);
131DebugPrint ("msg.gas = ", gasleft());
132
133DebugPrint ("1 + 2 = ", DemoLibrary.DLadd (1, 2));
134
135PrintInfo();
136}
137
138// Переопределяем виртуальную функцию из базового класса.
139function GetContractName () internal override pure returns (string memory) {
140
141return contractName;
142}
143
144function PureFunction (uint a) pure public NotNull(a) returns (uint) {
145console.log ("a = %d\n", a);
146return a + 1;
147}
148
149function TestBytes (bytes calldata a) public returns (bytes calldata) {
150
151//uint slot = DebugStorageContract.GetStorageSlot (b1);
152PrintBytes (a);
153
154PrintInfo();
155return a;
156}
157
158function TestVariables () public {
159
160uint slot;
161uint offset;
162//assembly {slot := contractName.slot}
163//DebugPrint ("contractName slot: ", slot);
164
165// Демонстрация размещения статических переменных в storage
166
167assembly { slot := ui8.slot // адрес 256-битного слота в storage
168offset := ui8.offset} // смещение в байтах внутри слота
169DebugPrint ("ui8 slot, offset: ", slot, offset);
170
171assembly { slot := ui16.slot
172offset := ui16.offset}
173DebugPrint ("ui16 slot, offset: ", slot, offset);
174
175assembly { slot := ui24.slot
176offset := ui24.offset}
177DebugPrint ("ui24 slot, offset: ", slot, offset);
178
179assembly { slot := ui256.slot
180offset := ui256.offset}
181DebugPrint ("ui256 slot, offset: ", slot, offset);
182
183assembly { slot := i8.slot
184offset := i8.offset}
185DebugPrint ("i8 slot, offset: ", slot, offset);
186
187assembly { slot := i256.slot
188offset := i256.offset}
189DebugPrint ("i256 slot, offset: ", slot, offset);
190
191assembly { slot := b1.slot
192offset := b1.offset}
193DebugPrint ("b1 slot, offset: ", slot, offset);
194
195assembly { slot := b2.slot
196offset := b2.offset}
197DebugPrint ("b2 slot, offset: ", slot, offset);
198
199assembly { slot := b3.slot
200offset := b3.offset}
201DebugPrint ("b3 slot, offset: ", slot, offset);
202
203assembly { slot := b4.slot
204offset := b4.offset}
205DebugPrint ("b5 slot, offset: ", slot, offset);
206
207assembly { slot := b32.slot
208offset := b32.offset}
209DebugPrint ("b32 slot, offset: ", slot, offset);
210
211assembly { slot := uia2.slot
212offset := uia2.offset}
213DebugPrint ("uia2 slot, offset: ", slot, offset);
214
215assembly { slot := uida.slot
216offset := uida.offset}
217DebugPrint ("uida slot, offset: ", slot, offset);
218
219assembly { slot := ba.slot
220offset := ba.offset}
221DebugPrint ("ba slot, offset: ", slot, offset);
222
223assembly { slot := uumap.slot
224offset := uumap.offset}
225DebugPrint ("uumap slot, offset: ", slot, offset);
226
227assembly { slot := sea5.slot
228offset := sea5.offset}
229DebugPrint ("sea5 slot, offset: ", slot, offset);
230
231DebugStorageContract.PrintStorage (0, 12);
232
233
234// Демонстрация размещения динамических переменных в storage
235
236uint uidaSlot;
237assembly { uidaSlot := uida.slot }
238
239// в слоте массива сейчас нулевая длина
240DebugPrint ("uida slot: ", uidaSlot, DebugStorageContract.ReadStorageValue (uidaSlot));
241
242// помещаем два элемента
243uida.push (1111111111111111111111111111111111111111111112222);
244uida.push (2222222222222222222222222222222222222222222223333);
245
246// в слоте массива сейчас 2
247DebugPrint ("uida slot: ", uidaSlot, DebugStorageContract.ReadStorageValue (uidaSlot));
248
249for (uint i = 0; i < uida.length; ++i) {
250// высчитываем адрес слота для i-го элемента
251uint elementSlot = uint256(keccak256(abi.encodePacked(uidaSlot))) + i * 1;
252// выводим значение по адресу
253DebugPrint ("uida element slot ", elementSlot, DebugStorageContract.ReadStorageValue (elementSlot));
254}
255
256
257uint uumapSlot;
258assembly { uumapSlot := uumap.slot }
259
260// в слоте отображения всегда 0
261DebugPrint ("uumap slot: ", uumapSlot, DebugStorageContract.ReadStorageValue (uumapSlot));
262
263// помещаем два элемента
264uumap[0] = 3333333333333333333333333333333333333333333334444;
265uumap[1] = 4444444444444444444444444444444444444444444445555;
266
267// высчитываем адрес слота для uumap[0]
268uint element0Slot = uint256(keccak256(abi.encodePacked(uint(0), uumapSlot)));
269// выводим значение по адресу
270DebugPrint ("uumap[0] element slot ", element0Slot, DebugStorageContract.ReadStorageValue (element0Slot));
271
272// высчитываем адрес слота для uumap[1]
273uint element1Slot = uint256(keccak256(abi.encodePacked(uint(1), uumapSlot)));
274// выводим значение по адресу
275DebugPrint ("uumap[1] element slot ", element1Slot, DebugStorageContract.ReadStorageValue (element1Slot));
276
277
278// Перепишем элементом динамического массива uida элемент отображения uumap.
279// Адреса их слотов мы только что подсчитали.
280// Слоты элементов динамического массива идут последовательно, начиная с некоторого.
281// Поэтому надо просто вычислить индекс массива, который будет попадать
282// в нужный элемент отображения uumap.
283
284// Переписываем поле длины массива максимальным значением
285DebugStorageContract.WriteStorageValue (uidaSlot, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
286// адрес слота 0-го элемента массива
287uint uidaElement0Slot = uint256(keccak256(abi.encodePacked(uidaSlot)));
288
289// высчитываем индекс элемента массива, который попадает в тот же слот, что и uumap[1]
290uint writeIndex;
291unchecked {
292writeIndex = element1Slot - uidaElement0Slot;
293}
294
295DebugPrint ("writeIndex: ", writeIndex);
296
297// выводим элемент до перезаписи
298DebugPrint ("uumap[1]: ", uumap[1]);
299// перезаписываем
300uida[writeIndex] = 5555555555555555555555555555555555555555555556666;
301// выводим тот же элемент после перезаписи
302DebugPrint ("uumap[1]: ", uumap[1]);
303
304// Можно заметить, что mapping также позволяет писать по произвольным адресам
305// в storage, к тому же в нём нет ограничений наподобие длины массива.
306// Но если мы хотим переписать элемент в storage по фиксированному адресу,
307// то вычислительно невозможно подобрать ключ, который отобразиться на этот элемент.
308
309}
310
311}
312