ethereum_smartcontract_intro

Форк
0
372 строки · 16.3 Кб
1
// SPDX-License-Identifier: UNLICENSED
2

3
pragma solidity >=0.8;
4

5

6
import "./Hello.sol";
7
import "./Debug.sol";
8

9
import "hardhat/console.sol";
10

11

12
// Контракт для создания других контрактов из байткода.
13
abstract contract FactoryFromBytecode {
14

15
    function deploy (bytes calldata bytecode) public returns (address addr) {
16

17
        // Копируем байты из входных данных в память.
18
        bytes memory implInitCode = bytecode;
19
        //uint bytecodeSize = bytecode.length;
20
        assembly {
21
            let encoded_data := add(0x20, implInitCode)
22
            let encoded_size := mload(implInitCode)
23
            addr := create (0, encoded_data, encoded_size)
24
        }
25

26
    }
27

28
    function deploy2 (uint salt, bytes calldata bytecode) public returns (address addr)  {
29
        bytes memory implInitCode = bytecode;
30
        
31
        uint bytecodeSize = bytecode.length;
32
        uint encoded_size;
33
        assembly {
34
            // Первые 20 байт в массиве - его длина.
35

36
            // получаем адрес в памяти байткода
37
            let encoded_data := add(0x20, implInitCode) 
38
            // считываем из памяти длину байткода
39
            encoded_size := mload(implInitCode)
40
            addr := create2(0, encoded_data, encoded_size, salt)
41
        }
42
        console.log ("%s %s %s\n", addr, encoded_size, bytecodeSize);
43

44
    }
45
}
46

47

48
contract CallContract is DebugPrintContract, FactoryFromBytecode {
49

50
    uint startValue = 111110;
51

52
    constructor () payable {
53

54
        DebugPrint ("gasleft: ", gasleft());
55

56
    }
57

58
    function GetContractName () internal override pure returns (string memory) {
59
        return "CallContract";
60
    }
61

62
    function CreateHello (uint gas) public {
63
        
64
        string memory msgStr = GetContractName();
65

66
        // Создаём новый контракт HelloWorld инструкцией create.
67
        // И передаём ему с нашего баланса 1 ether.
68
        HelloWorld hc = (new HelloWorld){value: 1 ether} ();
69
        console.log ("[%s] Create hello: %s\n", msgStr, address(hc));
70

71
        // Вызов функций возможен через переменную с типом контракта.
72
        // Тогда компилятор самостоятельно сформирует данные для инструкции call.
73

74
        // В вызов функции Hello передаётся весь оставшийся газ.
75
        hc.Hello (1);
76

77
        // Вызов функции Hello с передачей эфира и указанием газа.
78
        hc.Hello{value: 1 ether, gas: 5000} (2);
79
        
80
        // Если в вызов передать слишком мало газа, то он закончится с ошибкой,
81
        // инстукций call вернёт false и код компилятор в этом месте проверит это
82
        // и вызовет revert.
83
        //hc.Hello{value: 1 ether, gas: 100} (3);
84
        hc.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()} ("");
91
        console.log ("[%s] res = %s\n", msgStr, retCode);
92

93
        // Передаём селектор несуществующей функции - вызовется функция fallback.
94
        (retCode, result) = address(hc).call{value: 1 ether} (abi.encodeWithSignature("Hello(uint)", 5));
95
        console.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));
99
        console.log ("[%s] res = %s\n", msgStr, retCode);
100

101
        // Вызов функции ShowStorageValue, которая работает с постоянным хранилищем контракта.
102
        // через call
103
        hc.ShowStorageValue();
104
        // через delegatecall
105
        (retCode, result) = address(hc).delegatecall (abi.encodeWithSignature("ShowStorageValue()"));
106
        console.log ("[%s] res = %s\n", msgStr, retCode);
107

108

109
        hc.SelfDestroy();
110
    }
111

112
    function CreateHello2 () public {
113
        
114
        string memory msgStr = GetContractName();
115

116
        // Создаём новый контракт HelloWorld инструкцией create.
117
        HelloWorld hello1 = (new HelloWorld){value: 1 ether} ();
118
        console.log ("[%s] Create hello1: %s\n", msgStr, address(hello1));
119
        hello1.SelfDestroy();
120

121
        // Создаём новый контракт HelloWorld инструкцией create2.
122
        HelloWorld hello2 = (new HelloWorld){value: 1 ether, salt: hex"01020304"} ();
123
        console.log ("[%s] Create hello2: %s\n", msgStr, address(hello2));
124
        hello2.SelfDestroy();
125

126
    }
127

128
    // Создаёт контракт HelloWorld из переданного байткода.
129
    // Надо передать код инициализации. Можно посмотреть этот код в Remix.
130
    function CreateHello3 (bytes calldata initBytecode) public {
131
        
132
        PrintBytes (initBytecode);
133

134
        HelloWorld hello3 = HelloWorld (payable(deploy (initBytecode)));
135
        DebugPrint ("hello3: ", uint(uint160(address(hello3))));
136
        //hello3.Hello (1);
137
    }
138

139
    function CreateHello4 (uint salt, bytes calldata initBytecode) public {
140
        
141
        PrintBytes (initBytecode);
142

143
        HelloWorld hello4 = HelloWorld (payable(deploy2 (salt, initBytecode)));
144
        DebugPrint ("hello3: ", uint(uint160(address(hello4))));
145
        //hello4.Hello (1);
146
    }
147

148
}
149

150

151
contract CallContract2 is DebugPrintContract {
152

153
    uint startValue = 222220;
154

155
    constructor () payable {
156

157
    }
158

159
    function GetContractName () internal override pure returns (string memory) {
160
        return "CallContract2";
161
    }
162

163
    // Вызов с помощью call в другом контракте функции, вызывающей revert.
164
    function CallRevert () public {
165

166
        string memory msgStr = GetContractName();
167

168
        HelloWorld2 hc = (new HelloWorld2) ();
169

170
        // Вызываем разными инструкциями и с разными аргументами функцию Revert.
171
        // Она изменяет значение в storage и при некоторых аргументах вызывает revert.
172
        
173
        // Если использовать низкоуровневый вызов для адреса,
174
        // то ошибки в текущей функции не будет.
175
        DebugPrint ("call Revert(0)");
176
        (bool retCode, bytes memory result) = address(hc).call{value: 0 ether} (abi.encodeWithSignature("Revert(uint256)", 0));
177
        console.log ("[%s] res = %s\n", msgStr, retCode);
178
        DebugPrint ("call Revert(0)");
179
        (retCode, result) = address(hc).call{value: 0 ether} (abi.encodeWithSignature("Revert(uint256)", 0));
180
        console.log ("[%s] res = %s\n", msgStr, retCode);
181
        DebugPrint ("call Revert(1)");
182
        (retCode, result) = address(hc).call{value: 0 ether} (abi.encodeWithSignature("Revert(uint256)", 1));
183
        console.log ("[%s] res = %s\n", msgStr, retCode);
184
        DebugPrint ("call Revert(1)");
185
        (retCode, result) = address(hc).call{value: 0 ether} (abi.encodeWithSignature("Revert(uint256)", 1));
186
        console.log ("[%s] res = %s\n", msgStr, retCode);
187

188
        // А так будет ошибка текущей функции.
189
        // Транзакция к этой функции закончится с ошибкой,
190
        // но отладочный вывод до этого места увидим.
191
        DebugPrint ("hc.Revert");
192
        hc.Revert(1);
193

194
    }
195

196
    // Вызов с помощью staticcall в другом контракте функции, вызывающей revert.
197
    function StaticcallRevert () public {
198

199
        string memory msgStr = GetContractName();
200

201
        HelloWorld2 hc = (new HelloWorld2) ();
202
        
203
        // Если с помощью staticcall вызвать функцию, изменяющую состояние,
204
        // то функцию завершится с ошибкой, исчерпав весь переданный ей газ.
205
        // Поэтому если передавать в вызовы весь газ, то первый же вызов
206
        // с ошибкой исчерпает весь газ транзакции.
207
        DebugPrint ("staticcall Revert(0) gasleft = ", gasleft());
208
        (bool retCode, bytes memory result) = address(hc).staticcall (abi.encodeWithSignature("Revert(uint256)", 0));
209
        console.log ("[%s] res = %s\n", msgStr, retCode);
210
        DebugPrint ("staticcall Revert(0) gasleft = ", gasleft());
211
        
212
        // на этот вызов уже газа не хватит
213
        (retCode, result) = address(hc).staticcall (abi.encodeWithSignature("Revert(uint256)", 0));
214
        console.log ("[%s] res = %s\n", msgStr, retCode);
215
        DebugPrint ("staticcall Revert(1) gasleft = ", gasleft());
216
        (retCode, result) = address(hc).staticcall (abi.encodeWithSignature("Revert(uint256)", 1));
217
        console.log ("[%s] res = %s\n", msgStr, retCode);
218
        DebugPrint ("staticcall Revert(1) gasleft = ", gasleft());
219
        (retCode, result) = address(hc).staticcall (abi.encodeWithSignature("Revert(uint256)", 1));
220
        console.log ("[%s] res = %s\n", msgStr, retCode);
221

222
        DebugPrint ("hc.Revert");
223
        hc.Revert(1);
224

225
    }
226

227
    // Вызов с помощью staticcall в другом контракте функции, вызывающей revert.
228
    function StaticcallRevert2 () public {
229

230
        string memory msgStr = GetContractName();
231

232
        HelloWorld2 hc = (new HelloWorld2) ();
233
        
234
        // Здесь в вызов staticcall передаём небольшое количество газа.
235
        // В каждом вызове весь переданный газ сгорит, но в текущем вызове газ
236
        // останется для следующих вызовов.
237
        DebugPrint ("staticcall Revert(0) gasleft = ", gasleft());
238
        (bool retCode, bytes memory result) = address(hc).staticcall{gas: 1000} (abi.encodeWithSignature("Revert(uint256)", 0));
239
        console.log ("[%s] res = %s\n", msgStr, retCode);
240
        DebugPrint ("staticcall Revert(0) gasleft = ", gasleft());
241
        (retCode, result) = address(hc).staticcall{gas: 1000} (abi.encodeWithSignature("Revert(uint256)", 0));
242
        console.log ("[%s] res = %s\n", msgStr, retCode);
243
        DebugPrint ("staticcall Revert(1) gasleft = ", gasleft());
244
        (retCode, result) = address(hc).staticcall{gas: 1000} (abi.encodeWithSignature("Revert(uint256)", 1));
245
        console.log ("[%s] res = %s\n", msgStr, retCode);
246
        DebugPrint ("staticcall Revert(1) gasleft = ", gasleft());
247
        (retCode, result) = address(hc).staticcall{gas: 1000} (abi.encodeWithSignature("Revert(uint256)", 1));
248
        console.log ("[%s] res = %s\n", msgStr, retCode);
249

250
        DebugPrint ("hc.Revert");
251
        hc.Revert(1);
252

253
    }
254

255
    // Вызов с помощью delegatecall в другом контракте функции, вызывающей revert.
256
    function DelegatecallRevert () public {
257

258
        string memory msgStr = GetContractName();
259

260
        HelloWorld2 hc = (new HelloWorld2) ();
261
        
262
        // Вызываем функцию через delegatecall.
263
        // Значит функция изменит в storage текущего контракта переменную startValue.
264

265
        // Первые два вызова пройдут удачно
266
        DebugPrint ("delegatecall Revert(0)");
267
        (bool retCode, bytes memory result) = address(hc).delegatecall (abi.encodeWithSignature("Revert(uint256)", 0));
268
        console.log ("[%s] res = %s\n", msgStr, retCode);
269
        DebugPrint ("delegatecall Revert(0)");
270
        (retCode, result) = address(hc).delegatecall (abi.encodeWithSignature("Revert(uint256)", 0));
271
        console.log ("[%s] res = %s\n", msgStr, retCode);
272
        // к этом моменту переменная 2 раза изменится
273

274
        // Следующие два вызова пройдут неудачно.
275
        DebugPrint ("delegatecall Revert(1)");
276
        (retCode, result) = address(hc).delegatecall (abi.encodeWithSignature("Revert(uint256)", 1));
277
        console.log ("[%s] res = %s\n", msgStr, retCode);
278
        DebugPrint ("delegatecall Revert(1)");
279
        (retCode, result) = address(hc).delegatecall (abi.encodeWithSignature("Revert(uint256)", 1));
280
        console.log ("[%s] res = %s\n", msgStr, retCode);
281
        // т.е. после них значение переменной не изменится.
282

283

284
        // Здесь транзакция завершится с ошибкой,
285
        // а значит отбросятся все изменения состояния.
286
        // Даже те, которые произошли в двух первых (удачных) вызовах.
287
        DebugPrint ("hc.Revert");
288
        hc.Revert (1);
289

290
    }
291

292
    function Resend () public payable {
293

294
        HelloWorld2 hc = (new HelloWorld2) ();
295

296
        // Пришедшие деньги сразу можем передать другому адресу
297
        bool ret = payable(address(hc)).send (msg.value);
298
        DebugPrint ("send: ret = ", ret ? uint(1) : uint(0));
299
    }
300

301
    function Retransfer () public payable {
302

303
        HelloWorld2 hc = (new HelloWorld2) ();
304

305
        // Пришедшие деньги сразу можем передать другому адресу
306
        payable(address(hc)).transfer (msg.value);
307
    }
308

309
}
310

311

312
interface IFunInterface {
313

314
    function fun (uint a) external;
315
}
316

317

318
interface IHelloWorld3 {
319

320
    // В сигнатуре указываем модификатор payable.
321
    // Хотя в контракте его у этой функции нет.
322
    // Но модификаторы не входят в сигнатуру для формирования селектора.
323
    function NotPayableFunction() external payable;
324
}
325

326

327
contract CallContract3 {
328

329
    function ResendNotPayableFunction () public payable {
330

331
        HelloWorld3 hc = (new HelloWorld3) ();
332

333
        // Передаём деньги в функцию receive.
334
        (bool retCode, bytes memory result) = payable(address(hc)).call{value: msg.value / 3} ("");
335
        console.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()"));
339
        console.log ("[CallContract3] NotPayableFunction: retCode = %s\n", retCode);
340

341
        // Вызываем функцию NotPayableFunction без денег - вернёт true.
342
        IHelloWorld3(address(hc)).NotPayableFunction();
343

344
        // Передаём деньги в функцию NotPayableFunction - вернёт false.
345
        // Поэтому будет ошибка текущего вызова.
346
        IHelloWorld3(address(hc)).NotPayableFunction{value: msg.value / 3}();
347
    }
348

349
    function fun (uint a) external {
350

351
        console.log ("[CallContract3] fun: a = %s\n", a);
352
    }
353

354
    function DemoFun() public {
355

356
        HelloWorld2 hello2 = (new HelloWorld2) ();
357
        HelloWorld3 hello3 = (new HelloWorld3) ();
358

359
        IFunInterface(address(hello2)).fun (1);
360
        IFunInterface(address(hello3)).fun (2);
361
        IFunInterface(address(this)).fun (3);
362

363
        // При вызове с помощью инструкции call
364
        // внутри будет вызвана функция HelloWorld3.fun
365
        hello3.ProxyFun (4);
366

367
        // При вызове с помощью инструкции delegatecall
368
        // внутри будет вызвана функция CallContract3.fun
369
        address(hello3).delegatecall (abi.encodeWithSignature("ProxyFun(uint256)", 5));
370
    }
371

372
}

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.