ethereum_smartcontract_intro
372 строки · 16.3 Кб
1// SPDX-License-Identifier: UNLICENSED
2
3pragma solidity >=0.8;
4
5
6import "./Hello.sol";
7import "./Debug.sol";
8
9import "hardhat/console.sol";
10
11
12// Контракт для создания других контрактов из байткода.
13abstract contract FactoryFromBytecode {
14
15function deploy (bytes calldata bytecode) public returns (address addr) {
16
17// Копируем байты из входных данных в память.
18bytes memory implInitCode = bytecode;
19//uint bytecodeSize = bytecode.length;
20assembly {
21let encoded_data := add(0x20, implInitCode)
22let encoded_size := mload(implInitCode)
23addr := create (0, encoded_data, encoded_size)
24}
25
26}
27
28function deploy2 (uint salt, bytes calldata bytecode) public returns (address addr) {
29bytes memory implInitCode = bytecode;
30
31uint bytecodeSize = bytecode.length;
32uint encoded_size;
33assembly {
34// Первые 20 байт в массиве - его длина.
35
36// получаем адрес в памяти байткода
37let encoded_data := add(0x20, implInitCode)
38// считываем из памяти длину байткода
39encoded_size := mload(implInitCode)
40addr := create2(0, encoded_data, encoded_size, salt)
41}
42console.log ("%s %s %s\n", addr, encoded_size, bytecodeSize);
43
44}
45}
46
47
48contract CallContract is DebugPrintContract, FactoryFromBytecode {
49
50uint startValue = 111110;
51
52constructor () payable {
53
54DebugPrint ("gasleft: ", gasleft());
55
56}
57
58function GetContractName () internal override pure returns (string memory) {
59return "CallContract";
60}
61
62function CreateHello (uint gas) public {
63
64string memory msgStr = GetContractName();
65
66// Создаём новый контракт HelloWorld инструкцией create.
67// И передаём ему с нашего баланса 1 ether.
68HelloWorld hc = (new HelloWorld){value: 1 ether} ();
69console.log ("[%s] Create hello: %s\n", msgStr, address(hc));
70
71// Вызов функций возможен через переменную с типом контракта.
72// Тогда компилятор самостоятельно сформирует данные для инструкции call.
73
74// В вызов функции Hello передаётся весь оставшийся газ.
75hc.Hello (1);
76
77// Вызов функции Hello с передачей эфира и указанием газа.
78hc.Hello{value: 1 ether, gas: 5000} (2);
79
80// Если в вызов передать слишком мало газа, то он закончится с ошибкой,
81// инстукций call вернёт false и код компилятор в этом месте проверит это
82// и вызовет revert.
83//hc.Hello{value: 1 ether, gas: 100} (3);
84hc.Hello{value: 1 ether, gas: gas} (4);
85
86// Функции можно вызывать через низкоуровневый интерфейс для адреса.
87// Тогда входные данные нужно формировать самостоятельно
88
89// Передём пустые входные данные - вызовется функция receive.
90(bool retCode, bytes memory result) = address(hc).call{value: 1 ether, gas: gasleft()} ("");
91console.log ("[%s] res = %s\n", msgStr, retCode);
92
93// Передаём селектор несуществующей функции - вызовется функция fallback.
94(retCode, result) = address(hc).call{value: 1 ether} (abi.encodeWithSignature("Hello(uint)", 5));
95console.log ("[%s] res = %s\n", msgStr, retCode);
96
97// Вызов функции Hello с аргументом 6
98(retCode, result) = address(hc).call{value: 1 ether} (abi.encodeWithSignature("Hello(uint256)", 6));
99console.log ("[%s] res = %s\n", msgStr, retCode);
100
101// Вызов функции ShowStorageValue, которая работает с постоянным хранилищем контракта.
102// через call
103hc.ShowStorageValue();
104// через delegatecall
105(retCode, result) = address(hc).delegatecall (abi.encodeWithSignature("ShowStorageValue()"));
106console.log ("[%s] res = %s\n", msgStr, retCode);
107
108
109hc.SelfDestroy();
110}
111
112function CreateHello2 () public {
113
114string memory msgStr = GetContractName();
115
116// Создаём новый контракт HelloWorld инструкцией create.
117HelloWorld hello1 = (new HelloWorld){value: 1 ether} ();
118console.log ("[%s] Create hello1: %s\n", msgStr, address(hello1));
119hello1.SelfDestroy();
120
121// Создаём новый контракт HelloWorld инструкцией create2.
122HelloWorld hello2 = (new HelloWorld){value: 1 ether, salt: hex"01020304"} ();
123console.log ("[%s] Create hello2: %s\n", msgStr, address(hello2));
124hello2.SelfDestroy();
125
126}
127
128// Создаёт контракт HelloWorld из переданного байткода.
129// Надо передать код инициализации. Можно посмотреть этот код в Remix.
130function CreateHello3 (bytes calldata initBytecode) public {
131
132PrintBytes (initBytecode);
133
134HelloWorld hello3 = HelloWorld (payable(deploy (initBytecode)));
135DebugPrint ("hello3: ", uint(uint160(address(hello3))));
136//hello3.Hello (1);
137}
138
139function CreateHello4 (uint salt, bytes calldata initBytecode) public {
140
141PrintBytes (initBytecode);
142
143HelloWorld hello4 = HelloWorld (payable(deploy2 (salt, initBytecode)));
144DebugPrint ("hello3: ", uint(uint160(address(hello4))));
145//hello4.Hello (1);
146}
147
148}
149
150
151contract CallContract2 is DebugPrintContract {
152
153uint startValue = 222220;
154
155constructor () payable {
156
157}
158
159function GetContractName () internal override pure returns (string memory) {
160return "CallContract2";
161}
162
163// Вызов с помощью call в другом контракте функции, вызывающей revert.
164function CallRevert () public {
165
166string memory msgStr = GetContractName();
167
168HelloWorld2 hc = (new HelloWorld2) ();
169
170// Вызываем разными инструкциями и с разными аргументами функцию Revert.
171// Она изменяет значение в storage и при некоторых аргументах вызывает revert.
172
173// Если использовать низкоуровневый вызов для адреса,
174// то ошибки в текущей функции не будет.
175DebugPrint ("call Revert(0)");
176(bool retCode, bytes memory result) = address(hc).call{value: 0 ether} (abi.encodeWithSignature("Revert(uint256)", 0));
177console.log ("[%s] res = %s\n", msgStr, retCode);
178DebugPrint ("call Revert(0)");
179(retCode, result) = address(hc).call{value: 0 ether} (abi.encodeWithSignature("Revert(uint256)", 0));
180console.log ("[%s] res = %s\n", msgStr, retCode);
181DebugPrint ("call Revert(1)");
182(retCode, result) = address(hc).call{value: 0 ether} (abi.encodeWithSignature("Revert(uint256)", 1));
183console.log ("[%s] res = %s\n", msgStr, retCode);
184DebugPrint ("call Revert(1)");
185(retCode, result) = address(hc).call{value: 0 ether} (abi.encodeWithSignature("Revert(uint256)", 1));
186console.log ("[%s] res = %s\n", msgStr, retCode);
187
188// А так будет ошибка текущей функции.
189// Транзакция к этой функции закончится с ошибкой,
190// но отладочный вывод до этого места увидим.
191DebugPrint ("hc.Revert");
192hc.Revert(1);
193
194}
195
196// Вызов с помощью staticcall в другом контракте функции, вызывающей revert.
197function StaticcallRevert () public {
198
199string memory msgStr = GetContractName();
200
201HelloWorld2 hc = (new HelloWorld2) ();
202
203// Если с помощью staticcall вызвать функцию, изменяющую состояние,
204// то функцию завершится с ошибкой, исчерпав весь переданный ей газ.
205// Поэтому если передавать в вызовы весь газ, то первый же вызов
206// с ошибкой исчерпает весь газ транзакции.
207DebugPrint ("staticcall Revert(0) gasleft = ", gasleft());
208(bool retCode, bytes memory result) = address(hc).staticcall (abi.encodeWithSignature("Revert(uint256)", 0));
209console.log ("[%s] res = %s\n", msgStr, retCode);
210DebugPrint ("staticcall Revert(0) gasleft = ", gasleft());
211
212// на этот вызов уже газа не хватит
213(retCode, result) = address(hc).staticcall (abi.encodeWithSignature("Revert(uint256)", 0));
214console.log ("[%s] res = %s\n", msgStr, retCode);
215DebugPrint ("staticcall Revert(1) gasleft = ", gasleft());
216(retCode, result) = address(hc).staticcall (abi.encodeWithSignature("Revert(uint256)", 1));
217console.log ("[%s] res = %s\n", msgStr, retCode);
218DebugPrint ("staticcall Revert(1) gasleft = ", gasleft());
219(retCode, result) = address(hc).staticcall (abi.encodeWithSignature("Revert(uint256)", 1));
220console.log ("[%s] res = %s\n", msgStr, retCode);
221
222DebugPrint ("hc.Revert");
223hc.Revert(1);
224
225}
226
227// Вызов с помощью staticcall в другом контракте функции, вызывающей revert.
228function StaticcallRevert2 () public {
229
230string memory msgStr = GetContractName();
231
232HelloWorld2 hc = (new HelloWorld2) ();
233
234// Здесь в вызов staticcall передаём небольшое количество газа.
235// В каждом вызове весь переданный газ сгорит, но в текущем вызове газ
236// останется для следующих вызовов.
237DebugPrint ("staticcall Revert(0) gasleft = ", gasleft());
238(bool retCode, bytes memory result) = address(hc).staticcall{gas: 1000} (abi.encodeWithSignature("Revert(uint256)", 0));
239console.log ("[%s] res = %s\n", msgStr, retCode);
240DebugPrint ("staticcall Revert(0) gasleft = ", gasleft());
241(retCode, result) = address(hc).staticcall{gas: 1000} (abi.encodeWithSignature("Revert(uint256)", 0));
242console.log ("[%s] res = %s\n", msgStr, retCode);
243DebugPrint ("staticcall Revert(1) gasleft = ", gasleft());
244(retCode, result) = address(hc).staticcall{gas: 1000} (abi.encodeWithSignature("Revert(uint256)", 1));
245console.log ("[%s] res = %s\n", msgStr, retCode);
246DebugPrint ("staticcall Revert(1) gasleft = ", gasleft());
247(retCode, result) = address(hc).staticcall{gas: 1000} (abi.encodeWithSignature("Revert(uint256)", 1));
248console.log ("[%s] res = %s\n", msgStr, retCode);
249
250DebugPrint ("hc.Revert");
251hc.Revert(1);
252
253}
254
255// Вызов с помощью delegatecall в другом контракте функции, вызывающей revert.
256function DelegatecallRevert () public {
257
258string memory msgStr = GetContractName();
259
260HelloWorld2 hc = (new HelloWorld2) ();
261
262// Вызываем функцию через delegatecall.
263// Значит функция изменит в storage текущего контракта переменную startValue.
264
265// Первые два вызова пройдут удачно
266DebugPrint ("delegatecall Revert(0)");
267(bool retCode, bytes memory result) = address(hc).delegatecall (abi.encodeWithSignature("Revert(uint256)", 0));
268console.log ("[%s] res = %s\n", msgStr, retCode);
269DebugPrint ("delegatecall Revert(0)");
270(retCode, result) = address(hc).delegatecall (abi.encodeWithSignature("Revert(uint256)", 0));
271console.log ("[%s] res = %s\n", msgStr, retCode);
272// к этом моменту переменная 2 раза изменится
273
274// Следующие два вызова пройдут неудачно.
275DebugPrint ("delegatecall Revert(1)");
276(retCode, result) = address(hc).delegatecall (abi.encodeWithSignature("Revert(uint256)", 1));
277console.log ("[%s] res = %s\n", msgStr, retCode);
278DebugPrint ("delegatecall Revert(1)");
279(retCode, result) = address(hc).delegatecall (abi.encodeWithSignature("Revert(uint256)", 1));
280console.log ("[%s] res = %s\n", msgStr, retCode);
281// т.е. после них значение переменной не изменится.
282
283
284// Здесь транзакция завершится с ошибкой,
285// а значит отбросятся все изменения состояния.
286// Даже те, которые произошли в двух первых (удачных) вызовах.
287DebugPrint ("hc.Revert");
288hc.Revert (1);
289
290}
291
292function Resend () public payable {
293
294HelloWorld2 hc = (new HelloWorld2) ();
295
296// Пришедшие деньги сразу можем передать другому адресу
297bool ret = payable(address(hc)).send (msg.value);
298DebugPrint ("send: ret = ", ret ? uint(1) : uint(0));
299}
300
301function Retransfer () public payable {
302
303HelloWorld2 hc = (new HelloWorld2) ();
304
305// Пришедшие деньги сразу можем передать другому адресу
306payable(address(hc)).transfer (msg.value);
307}
308
309}
310
311
312interface IFunInterface {
313
314function fun (uint a) external;
315}
316
317
318interface IHelloWorld3 {
319
320// В сигнатуре указываем модификатор payable.
321// Хотя в контракте его у этой функции нет.
322// Но модификаторы не входят в сигнатуру для формирования селектора.
323function NotPayableFunction() external payable;
324}
325
326
327contract CallContract3 {
328
329function ResendNotPayableFunction () public payable {
330
331HelloWorld3 hc = (new HelloWorld3) ();
332
333// Передаём деньги в функцию receive.
334(bool retCode, bytes memory result) = payable(address(hc)).call{value: msg.value / 3} ("");
335console.log ("[CallContract3] receive: retCode = %s\n", retCode);
336
337// Передаём деньги в функцию NotPayableFunction - вернёт false.
338(retCode, result) = payable(address(hc)).call{value: msg.value / 3} (abi.encodeWithSignature("NotPayableFunction()"));
339console.log ("[CallContract3] NotPayableFunction: retCode = %s\n", retCode);
340
341// Вызываем функцию NotPayableFunction без денег - вернёт true.
342IHelloWorld3(address(hc)).NotPayableFunction();
343
344// Передаём деньги в функцию NotPayableFunction - вернёт false.
345// Поэтому будет ошибка текущего вызова.
346IHelloWorld3(address(hc)).NotPayableFunction{value: msg.value / 3}();
347}
348
349function fun (uint a) external {
350
351console.log ("[CallContract3] fun: a = %s\n", a);
352}
353
354function DemoFun() public {
355
356HelloWorld2 hello2 = (new HelloWorld2) ();
357HelloWorld3 hello3 = (new HelloWorld3) ();
358
359IFunInterface(address(hello2)).fun (1);
360IFunInterface(address(hello3)).fun (2);
361IFunInterface(address(this)).fun (3);
362
363// При вызове с помощью инструкции call
364// внутри будет вызвана функция HelloWorld3.fun
365hello3.ProxyFun (4);
366
367// При вызове с помощью инструкции delegatecall
368// внутри будет вызвана функция CallContract3.fun
369address(hello3).delegatecall (abi.encodeWithSignature("ProxyFun(uint256)", 5));
370}
371
372}