В предыдущей статье мы разобрались, как устроено хранение mapping’ов в EVM на примере простых типов. Мы научились записывать данные в маппинг и читать.
Однако, в смарт-контрактах, чтобы что-то записать в маппинг или прочитать из маппинга, мы пишем метод. А в метод передаем аргументы. Поэтому нам необходимо узнать, как передавать аргументы в метод.
Предыдущая статья. Следующая статья.
Полный список статей по теме тут.
Заходите в наш телеграмм канал — Blockchain Witnesses! Делитесь опытом или задавайте вопросы, если что-то непонятно.
Передача аргумента в метод
Давайте напишем контракт с методом, который возвращает то, что ему передали. Передавать будем простой тип uint.
| 
					 1 2 3 4 5 6 7 8 9 10 11  | 
						// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.25; contract BlockwitEmptyContract {     function pingPong(uint value) pure public returns( uint) {         return value;         } }  | 
					
Как мы уже делали в предыдущих статьях:
- Скомпилируем в Remix и скопируем байткод
 - Уберем из байткода метаданные, чтобы глаз не мозолили
 - Декомпилируем
 - Поправим опкоды, которые не распознал декомпилятор
 
В итоге получаем байткод:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212  | 
						// Конструктор label_0000: 	0000    60  PUSH1 0x80 	0002    60  PUSH1 0x40 	0004    52  MSTORE 	0005    34  CALLVALUE 	0006    80  DUP1 	0007    15  ISZERO 	0008    60  PUSH1 0x0e 	000A    57  JUMPI 	000B    5F  PUSH0 	000C    80  DUP1 	000D    FD  REVERT label_000E: 	000E    5B  JUMPDEST 	000F    50  POP 	0010    61  PUSH2 0x010f 	0013    80  DUP1 	0014    61  PUSH2 0x001c 	0017    5F  PUSH0 	0018    39  CODECOPY 	0019    5F  PUSH0 	001A    F3  RETURN 	001B    FE  ASSERT // Эффективный код смарт-контракта	 	001C    60    PUSH1 0x80 	001E    60    PUSH1 0x40 	0020    52    MSTORE 	0021    34    CALLVALUE 	0022    80    DUP1 	0023    15    ISZERO 	0024    60    PUSH1 0x0e 	0026    57    JUMPI 	0027    5F    5F 	0028    80    DUP1 	0029    FD    REVERT // Селектор методов		 	002A    5B    JUMPDEST 	002B    50    POP 	002C    60    PUSH1 0x04 	002E    36    CALLDATASIZE 	002F    10    LT 	0030    60    PUSH1 0x26 	0032    57    *JUMPI 	0033    5F    5F 	0034    35    CALLDATALOAD 	0035    60    PUSH1 0xe0 	0037    1C    SHR 	0038    80    DUP1 	0039    63    PUSH4 0x4a51e107 // хэш нашей функции 	003E    14    EQ 	003F    60    PUSH1 0x2a 	0041    57    JUMPI // переход на 0x2a, 0x2a + 0x1c (размер конструктора) = 0x46 	0042    5B    JUMPDEST 	0043    5F    5F 	0044    80    DUP1 	0045    FD    REVERT // Код нашей функции	 	0046    5B    JUMPDEST 	0047    60    PUSH1 0x40 	0049    60    PUSH1 0x04 	004B    80    DUP1 	004C    36    CALLDATASIZE 	004D    03    SUB 	004E    81    DUP2 	004F    01    ADD 	0050    90    SWAP1 	0051    60    PUSH1 0x3c 	0053    91    SWAP2 	0054    90    SWAP1 	0055    60    PUSH1 0x8f 	0057    56    JUMP 	0058    5B    JUMPDEST 	0059    60    PUSH1 0x54 	005B    56    JUMP 	005C    5B    JUMPDEST 	005D    60    PUSH1 0x40 	005F    51    MLOAD 	0060    60    PUSH1 0x4b 	0062    91    SWAP2 	0063    90    SWAP1 	0064    60    PUSH1 0xc2 	0066    56    *JUMP 	0067    5B    JUMPDEST 	0068    60    PUSH1 0x40 	006A    51    MLOAD 	006B    80    DUP1 	006C    91    SWAP2 	006D    03    SUB 	006E    90    SWAP1 	006F    F3    *RETURN 	0070    5B    JUMPDEST 	0071    5F    5F 	0072    81    DUP2 	0073    90    SWAP1 	0074    50    POP 	0075    91    SWAP2 	0076    90    SWAP1 	0077    50    POP 	0078    56    JUMP 	0079    5B    JUMPDEST 	007A    5F    5F 	007B    80    DUP1 	007C    FD    *REVERT 	007D    5B    JUMPDEST 	007E    5F    5F 	007F    81    DUP2 	0080    90    SWAP1 	0081    50    POP 	0082    91    SWAP2 	0083    90    SWAP1 	0084    50    POP 	0085    56    *JUMP 	0086    5B    JUMPDEST 	0087    60    PUSH1 0x71 	0089    81    DUP2 	008A    60    PUSH1 0x61 	008C    56    *JUMP 	008D    5B    JUMPDEST 	008E    81    DUP2 	008F    14    EQ 	0090    60    PUSH1 0x7a 	0092    57    JUMPI 	0093    5F    PUSH0 	0094    80    DUP1 	0095    FD    REVERT 	0096    5B    JUMPDEST 	0097    50    POP 	0098    56    JUMP 	0099    5B    JUMPDEST 	009A    5F    PUSH0 	009B    81    DUP2 	009C    35    CALLDATALOAD 	009D    90    SWAP1 	009E    50    POP 	009F    60    PUSH1 0x89 	00A1    81    DUP2 	00A2    60    PUSH1 0x6a 	00A4    56    JUMP 	00A5    5B    JUMPDEST 	00A6    92    SWAP3 	00A7    91    SWAP2 	00A8    50    POP 	00A9    50    POP 	00AA    56    JUMP 	00AB    5B    JUMPDEST 	00AC    5F    PUSH0 	00AD    60    PUSH1 0x20 	00AF    82    DUP3 	00B0    84    DUP5 	00B1    03    SUB 	00B2    12    SLT 	00B3    15    ISZERO 	00B4    60    PUSH1 0xa1 	00B6    57    JUMPI 	00B7    60    PUSH1 0xa0 	00B9    60    PUSH1 0x5d 	00BB    56    JUMP 	00BC    5B    JUMPDEST 	00BD    5B    JUMPDEST 	00BE    5F    PUSH0 	00BF    60    PUSH1 0xac 	00C1    84    DUP5 	00C2    82    DUP3 	00C3    85    DUP6 	00C4    01    ADD 	00C5    60    PUSH1 0x7d 	00C7    56    JUMP 	00C8    5B    JUMPDEST 	00C9    91    SWAP2 	00CA    50    POP 	00CB    50    POP 	00CC    92    SWAP3 	00CD    91    SWAP2 	00CE    50    POP 	00CF    50    POP 	00D0    56    JUMP 	00D1    5B    JUMPDEST 	00D2    60    PUSH1 0xbc 	00D4    81    DUP2 	00D5    60    PUSH1 0x61 	00D7    56    JUMP 	00D8    5B    JUMPDEST 	00D9    82    DUP3 	00DA    52    MSTORE 	00DB    50    POP 	00DC    50    POP 	00DD    56    JUMP 	00DE    5B    JUMPDEST 	00DF    5F    PUSH0 	00E0    60    PUSH1 0x20 	00E2    82    DUP3 	00E3    01    ADD 	00E4    90    SWAP1 	00E5    50    POP 	00E6    60    PUSH1 0xd3 	00E8    5F    PUSH0 	00E9    83    DUP4 	00EA    01    ADD 	00EB    84    DUP5 	00EC    60    PUSH1 0xb5 	00EE    56    JUMP 	00EF    5B    JUMPDEST 	00F0    92    SWAP3 	00F1    91    SWAP2 	00F2    50    POP 	00F3    50    POP 	00F4    56    JUMP 	00F5    FE    ASSERT  | 
					
Из предыдущих статьей мы помним, что контракт содержит конструктор, блоки инициализации, проверки на перечисление wei. Все это мы уже умеем выделять. Нас интересует селектор методов. В нем мы выделяем код прыжка на нашу единственную функцию и таким образом, понимаем где начинается наша функция. А он начинается по адресу — 0x2a. Помним, что после копирования кода в блокчейн, код остается без конструктора, а значит адреса расположения инструкций смещаются. Давайте, поправим адреса, а заодно удалим все до начала нашей функции.
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151  | 
							002A    5B    JUMPDEST 	002B    60    PUSH1 0x40 	002D    60    PUSH1 0x04 	002F    80    DUP1 	0030    36    CALLDATASIZE 	0031    03    SUB 	0032    81    DUP2 	0033    01    ADD 	0034    90    SWAP1 	0035    60    PUSH1 0x3c 	0037    91    SWAP2 	0038    90    SWAP1 	0039    60    PUSH1 0x8f 	003B    56    JUMP 	003C    5B    JUMPDEST 	003D    60    PUSH1 0x54 	003F    56    JUMP 	0040    5B    JUMPDEST 	0041    60    PUSH1 0x40 	0043    51    MLOAD 	0044    60    PUSH1 0x4b 	0046    91    SWAP2 	0047    90    SWAP1 	0048    60    PUSH1 0xc2 	004A    56    JUMP 	004B    5B    JUMPDEST 	004C    60    PUSH1 0x40 	004E    51    MLOAD 	004F    80    DUP1 	0050    91    SWAP2 	0051    03    SUB 	0052    90    SWAP1 	0053    F3    *RETURN 	0054    5B    JUMPDEST 	0055    5F    5F 	0056    81    DUP2 	0057    90    SWAP1 	0058    50    POP 	0059    91    SWAP2 	005A    90    SWAP1 	005B    50    POP 	005C    56    JUMP 	005D    5B    JUMPDEST 	005E    5F    PUSH0 	005F    80    DUP1 	0060    FD    REVERT 	0061    5B    JUMPDEST 	0062    5F    PUSH0 	0063    81    DUP2 	0064    90    SWAP1 	0065    50    POP 	0066    91    SWAP2 	0067    90    SWAP1 	0068    50    POP 	0069    56    JUMP 	006A    5B    JUMPDEST 	006B    60    PUSH1 0x71 	006D    81    DUP2 	006E    60    PUSH1 0x61 	0070    56    JUMP 	0071    5B    JUMPDEST 	0072    81    DUP2 	0073    14    EQ 	0074    60    PUSH1 0x7a 	0076    57    JUMPI 	0077    5F    5F 	0078    80    DUP1 	0079    FD    REVERT 	007A    5B    JUMPDEST 	007B    50    POP 	007C    56    JUMP 	007D    5B    JUMPDEST 	007E    5F    PUSH0 	007F    81    DUP2 	0080    35    CALLDATALOAD 	0081    90    SWAP1 	0082    50    POP 	0083    60    PUSH1 0x89 	0085    81    DUP2 	0086    60    PUSH1 0x6a 	0088    56    JUMP 	0089    5B    JUMPDEST 	008A    92    SWAP3 	008B    91    SWAP2 	008C    50    POP 	008D    50    POP 	008E    56    JUMP 	008F    5B    JUMPDEST 	0090    5F    PUSH0 	0091    60    PUSH1 0x20 	0093    82    DUP3 	0094    84    DUP5 	0095    03    SUB 	0096    12    SLT 	0097    15    ISZERO 	0098    60    PUSH1 0xa1 	009A    57    JUMPI 	009B    60    PUSH1 0xa0 	009D    60    PUSH1 0x5d 	009F    56    JUMP 	00A0    5B    JUMPDEST 	00A1    5B    JUMPDEST 	00A2    5F    PUSH0 	00A3    60    PUSH1 0xac 	00A5    84    DUP5 	00A6    82    DUP3 	00A7    85    DUP6 	00A8    01    ADD 	00A9    60    PUSH1 0x7d 	00AB    56    JUMP 	00AC    5B    JUMPDEST 	00AD    91    SWAP2 	00AE    50    POP 	00AF    50    POP 	00B0    92    SWAP3 	00B1    91    SWAP2 	00B2    50    POP 	00B3    50    POP 	00B4    56    JUMP 	00B5    5B    JUMPDEST 	00B6    60    PUSH1 0xbc 	00B8    81    DUP2 	00B9    60    PUSH1 0x61 	00BB    56    JUMP 	00BC    5B    JUMPDEST 	00BD    82    DUP3 	00BE    52    MSTORE 	00BF    50    POP 	00C0    50    POP 	00C1    56    JUMP 	00C2    5B    JUMPDEST 	00C3    5F    PUSH0 	00C4    60    PUSH1 0x20 	00C6    82    DUP3 	00C7    01    ADD 	00C8    90    SWAP1 	00C9    50    POP 	00CA    60    PUSH1 0xd3 	00CC    5F    PUSH0 	00CD    83    DUP4 	00CE    01    ADD 	00CF    84    DUP5 	00D0    60    PUSH1 0xb5 	00D2    56    JUMP 	00D3    5B    JUMPDEST 	00D4    92    SWAP3 	00D5    91    SWAP2 	00D6    50    POP 	00D7    50    POP 	00D8    56    JUMP 	00D9    FE    ASSERT  | 
					
Разберем участок кода до первого прыжка:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14  | 
							002A    5B    JUMPDEST 	002B    60    PUSH1 0x40 	002D    60    PUSH1 0x04 	002F    80    DUP1 	0030    36    CALLDATASIZE 	0031    03    SUB 	0032    81    DUP2 	0033    01    ADD 	0034    90    SWAP1 	0035    60    PUSH1 0x3c 	0037    91    SWAP2 	0038    90    SWAP1 	0039    60    PUSH1 0x8f 	003B    56    JUMP  | 
					
Что тут происходит?
Мы помещаем в стэка два значения 0x40 и 0x04 и последнее дублируем. Получаем состояние стэка такое:
| 0x04 | 
| 0x04 | 
| 0x40 | 
Напомню, CALLDATASIZE — помещает в стэк размер сообщения msg.data. Т.е. размер данных, которые были переданы при вызове нашего смарт-контракта. После выполнения команды стэк станет таким:
| 0x24 | 
| 0x04 | 
| 0x04 | 
| 0x40 | 
Почему именно 0x24 ? Да потому, что мы при вызове нашей функции будут передаваться:
- Идентификатор вызываемой функции размером в 4 байта — 0x4a51e107
 - Аргумент типа unit, размер которого 32 байта или 0x20 в шестнадцатеричном представлении
 
В итоге 0x04 + 0x20 = 0x24.
Передаваемые в функцию аргументы записываются в msg.data 32 байтовыми слотами сразу после четырехбайтового идентификатора функции.
После этого идет команда SUB — она вычитает из первого аргумента в стэке второй. Результат помещает на стэк. Зачем это нужно? Таким образом мы получаем длину msg.data без четырехбайтового идентификатора функции. Т.е. длину всех аргументов. Сейчас нам это действие может казаться бессмысленным. Потому что мы с вами знаем длину аргументов. Но EVM, которая будет выполнять наш код заранее размер msg.data не знает! А при вызове смарт-контракта в msg.data могут передавать все что угодно, в том числе некорректные данные.
Итак, после SUB имеем:
| 0x20 | 
| 0x04 | 
| 0x20 | 
Далее идет ряд операций работы со стэком, среди которых одна команда ADD . Пока не очень понятно, что тут происходит. Давайте посмотрим, что будет после прыжка. Стэк после прыжка:
| 0x04 | 
| 0x24 | 
| 0x3c | 
| 0x20 | 
Код на который прыгнули:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13  | 
						        008F    5B    JUMPDEST 	0090    5F    PUSH0 	0091    60    PUSH1 0x20 	0093    82    DUP3 	0094    84    DUP5 	0095    03    SUB 	0096    12    SLT 	0097    15    ISZERO 	0098    60    PUSH1 0xa1 	009A    57    JUMPI 	009B    60    PUSH1 0xa0 	009D    60    PUSH1 0x5d 	009F    56    JUMP  | 
					
В этом коде есть интересная инструкция SLT. Она проверяет, что первый аргумент на стэке меньше второго. Если это так, то помещает на стэк единицу. Давайте посмотрим стэк до этой инструкции:
| 0x20 | 
| 0x20 | 
| 0x00 | 
| 0x04 | 
| 0x24 | 
| 0x3c | 
| 0x20 | 
Очевидно, что инструкция SLT даст нам 0. Это похоже на проверку кол-ва аргументов. И действительно, если бы первый аргумент на стэке был меньше второго, то мы бы прыгнули на 0x5f. А там у нас:
| 
					 1 2 3 4  | 
						005D 5B JUMPDEST 005E 5F PUSH0 005F 80 DUP1 0060 FD REVERT  | 
					
REVERT с возвратом пустого значения. Мы уже знаем, что REVERT в отличие от RETURN‘а, применяется, когда происходит ошибка. Т.е. да скорее всего наше предположении о проверке на кол-во аргументов по размеру верно. Т.е. если размер аргументов меньше 0x20, то мы выходим откатом — REVERT.
Возвращаемся к участку, с которого мы сюда прыгнули, пойдем по другой ветке. Прыгнем на 0xa1. После прыжка стэк будет таким:
| 0x01 | 
| 0x00 | 
| 0x04 | 
| 0x24 | 
| 0x3c | 
| 0x20 | 
Код после прыжка:
| 
					 1 2 3 4 5 6 7 8 9  | 
						00A1 5B JUMPDEST 00A2 5F PUSH0 00A3 60 PUSH1 0xac 00A5 84 DUP5 00A6 82 DUP3 00A7 85 DUP6 00A8 01 ADD 00A9 60 PUSH1 0x7d 00AB 56 JUMP  | 
					
Не похоже на то, что тут происходит что-то интересное, поэтому давайте просчитаем стэк и двинемся дальше. Стэк:
| 0x00 | 
| 0x04 | 
| 0xac | 
| 0x00 | 
| 0x01 | 
| 0x00 | 
| 0x04 | 
| 0x24 | 
| 0x3c | 
| 0x20 | 
Код после прыжка:
| 
					 1 2 3 4 5 6 7 8 9 10  | 
						        007D    5B    JUMPDEST 	007E    5F    PUSH0 	007F    81    DUP2 	0080    35    CALLDATALOAD 	0081    90    SWAP1 	0082    50    POP 	0083    60    PUSH1 0x89 	0085    81    DUP2 	0086    60    PUSH1 0x6a 	0088    56    JUMP  | 
					
А тут уже происходит интересное. А именно, присутствует инструкция CALLDATALOAD . Эта инструкция загружает 32 байта данных из msg.data в стэк. При этом адрес, с которого данные читаются из msg.data указан в стэке. Т.е, вероятно, этот код загружает наш аргумент. Давайте посмотрим на стэк перед вызововом CALLDATALOAD:
| 0x04 | 
| 0x00 | 
| …. | 
Как видим, на вершине стэка 0x04. А это значит, что CALLDATALOAD загрузит 32 байта из msg.data, которые находятся после 4 байт. Т.е. сразу после идентификатора функции. А сразу после идентификатора функции идет наш аргумент типа uint.
В принципе, дальше код можно не разбирать. Мы получили всю информацию о том, как работает передача аргументов в метод.
Передача аргументов происходит следующим образом:
- Получаем длину msg.data инструкцией CALLDATASIZE
 - Вычисляем длину всех аргументов. Для этого вычитаем из msg.data длины 4 — т.е. размер четырехбайтового идентификатора функции.
 - Проверяем, что длина всех аргументов не меньше длины аргументов, которые у нас принимает функция.
 - Загружаем аргументы в стэк начиная с адреса 0x04 инструкцией CALLDATALOAD
 
Ну а дальше, работа с полученными аргументами зависит от логики нашего метода. В случае сегодняшнего примера, мы теперь просто можем загрузить аргумент из стэка в память и сделать RETURN.
Резюме
В этой статье мы рассмотрели механизм передачи аргументов в метод. И теперь готовы написать что-то большее чем пустой бесполезный контракт. Чем мы и займемся в следующей статье.
Предыдущая статья. Следующая статья.
Полный список статей по теме тут.
Заходите в наш телеграмм канал — Blockchain Witnesses! Делитесь опытом или задавайте вопросы, если что-то непонятно.


