1
+ // # Simple UDT
2
+ //
3
+ // A simple UDT script using 128 bit unsigned integer range
4
+ //
5
+ // This UDT has 2 unlocking modes:
6
+ //
7
+ // 1. If one of the transaction input has a lock script matching the UDT
8
+ // script argument, the UDT script will be in owner mode. In owner mode no
9
+ // checks is performed, the owner can perform any operations such as issuing
10
+ // more UDTs or burning UDTs. By ensuring at least one transaction input has
11
+ // a matching lock script, the ownership of UDT can be ensured.
12
+ // 2. Otherwise, the UDT script will be in normal mode, where it ensures the
13
+ // sum of all input tokens is not smaller than the sum of all output tokens.
14
+ //
15
+ // Notice one caveat of this UDT script is that only one UDT can be issued
16
+ // for each unique lock script. A more sophisticated UDT script might include
17
+ // other arguments(such as the hash of the first input) as a unique identifier,
18
+ // however for the sake of simplicity, we are happy with this limitation.
19
+
20
+ // First, let's include header files used to interact with CKB.
21
+ #if defined(CKB_SIMULATOR )
22
+ #include "ckb_syscall_simulator.h"
23
+ #else
24
+ #include "ckb_syscalls.h"
25
+ #endif
26
+ #include "blockchain.h"
27
+
28
+ // We are limiting the script size loaded to be 32KB at most. This should be
29
+ // more than enough. We are also using blake2b with 256-bit hash here, which is
30
+ // the same as CKB.
31
+ #define BLAKE2B_BLOCK_SIZE 32
32
+ #define SCRIPT_SIZE 32768
33
+
34
+ // Common error codes that might be returned by the script.
35
+ #define ERROR_ARGUMENTS_LEN -1
36
+ #define ERROR_ENCODING -2
37
+ #define ERROR_SYSCALL -3
38
+ #define ERROR_SCRIPT_TOO_LONG -21
39
+ #define ERROR_OVERFLOWING -51
40
+ #define ERROR_AMOUNT -52
41
+
42
+ // We will leverage gcc's 128-bit integer extension here for number crunching.
43
+ typedef unsigned __int128 uint128_t ;
44
+
45
+ #ifdef CKB_SIMULATOR
46
+ int simulator_main () {
47
+ #else
48
+ int main () {
49
+ #endif
50
+ // First, let's load current running script, so we can extract owner lock
51
+ // script hash from script args.
52
+ unsigned char script [SCRIPT_SIZE ];
53
+ uint64_t len = SCRIPT_SIZE ;
54
+ int ret = ckb_load_script (script , & len , 0 );
55
+ if (ret != CKB_SUCCESS ) {
56
+ return ERROR_SYSCALL ;
57
+ }
58
+ if (len > SCRIPT_SIZE ) {
59
+ return ERROR_SCRIPT_TOO_LONG ;
60
+ }
61
+ mol_seg_t script_seg ;
62
+ script_seg .ptr = (uint8_t * )script ;
63
+ script_seg .size = len ;
64
+
65
+ if (MolReader_Script_verify (& script_seg , false) != MOL_OK ) {
66
+ return ERROR_ENCODING ;
67
+ }
68
+
69
+ mol_seg_t args_seg = MolReader_Script_get_args (& script_seg );
70
+ mol_seg_t args_bytes_seg = MolReader_Bytes_raw_bytes (& args_seg );
71
+ if (args_bytes_seg .size != BLAKE2B_BLOCK_SIZE ) {
72
+ return ERROR_ARGUMENTS_LEN ;
73
+ }
74
+
75
+ // With owner lock script extracted, we will look through each input in the
76
+ // current transaction to see if any unlocked cell uses owner lock.
77
+ int owner_mode = 0 ;
78
+ size_t i = 0 ;
79
+ while (1 ) {
80
+ uint8_t buffer [BLAKE2B_BLOCK_SIZE ];
81
+ uint64_t len = BLAKE2B_BLOCK_SIZE ;
82
+ // There are 2 points worth mentioning here:
83
+ //
84
+ // * First, we are using the checked version of CKB syscalls, the checked
85
+ // versions will return an error if our provided buffer is not enough to
86
+ // hold all returned data. This can help us ensure that we are processing
87
+ // enough data here.
88
+ // * Second, `CKB_CELL_FIELD_LOCK_HASH` is used here to directly load the
89
+ // lock script hash, so we don't have to manually calculate the hash again
90
+ // here.
91
+ ret = ckb_checked_load_cell_by_field (buffer , & len , 0 , i , CKB_SOURCE_INPUT ,
92
+ CKB_CELL_FIELD_LOCK_HASH );
93
+ if (ret == CKB_INDEX_OUT_OF_BOUND ) {
94
+ break ;
95
+ }
96
+ if (ret != CKB_SUCCESS ) {
97
+ return ret ;
98
+ }
99
+ if (len != BLAKE2B_BLOCK_SIZE ) {
100
+ return ERROR_ENCODING ;
101
+ }
102
+ if (memcmp (buffer , args_bytes_seg .ptr , BLAKE2B_BLOCK_SIZE ) == 0 ) {
103
+ owner_mode = 1 ;
104
+ break ;
105
+ }
106
+ i += 1 ;
107
+ }
108
+
109
+ // When owner mode is triggered, we won't perform any checks here, the owner
110
+ // is free to make any changes here, including token issurance, minting, etc.
111
+ if (owner_mode ) {
112
+ return CKB_SUCCESS ;
113
+ }
114
+
115
+ // When the owner mode is not enabled, however, we will then need to ensure
116
+ // the sum of all input tokens is not smaller than the sum of all output
117
+ // tokens. First, let's loop through all input cells containing current UDTs,
118
+ // and gather the sum of all input tokens.
119
+ uint128_t input_amount = 0 ;
120
+ i = 0 ;
121
+ while (1 ) {
122
+ uint128_t current_amount = 0 ;
123
+ len = 16 ;
124
+ // The implementation here does not require that the transaction only
125
+ // contains UDT cells for the current UDT type. It's perfectly fine to mix
126
+ // the cells for multiple different types of UDT together in one
127
+ // transaction. But that also means we need a way to tell one UDT type from
128
+ // another UDT type. The trick is in the `CKB_SOURCE_GROUP_INPUT` value used
129
+ // here. When using it as the source part of the syscall, the syscall would
130
+ // only iterate through cells with the same script as the current running
131
+ // script. Since different UDT types will naturally have different
132
+ // script(the args part will be different), we can be sure here that this
133
+ // loop would only iterate through UDTs that are of the same type as the one
134
+ // identified by the current running script.
135
+ //
136
+ // In the case that multiple UDT types are included in the same transaction,
137
+ // this simple UDT script will be run multiple times to validate the
138
+ // transaction, each time with a different script containing different
139
+ // script args, representing different UDT types.
140
+ //
141
+ // A different trick used here, is that our current implementation assumes
142
+ // that the amount of UDT is stored as unsigned 128-bit little endian
143
+ // integer in the first 16 bytes of cell data. Since RISC-V also uses little
144
+ // endian format, we can just read the first 16 bytes of cell data into
145
+ // `current_amount`, which is just an unsigned 128-bit integer in C. The
146
+ // memory layout of a C program will ensure that the value is set correctly.
147
+ ret = ckb_load_cell_data ((uint8_t * )& current_amount , & len , 0 , i ,
148
+ CKB_SOURCE_GROUP_INPUT );
149
+ // When `CKB_INDEX_OUT_OF_BOUND` is reached, we know we have iterated
150
+ // through all cells of current type.
151
+ if (ret == CKB_INDEX_OUT_OF_BOUND ) {
152
+ break ;
153
+ }
154
+ if (ret != CKB_SUCCESS ) {
155
+ return ret ;
156
+ }
157
+ if (len < 16 ) {
158
+ return ERROR_ENCODING ;
159
+ }
160
+ input_amount += current_amount ;
161
+ // Like any serious smart contract out there, we will need to check for
162
+ // overflows.
163
+ if (input_amount < current_amount ) {
164
+ return ERROR_OVERFLOWING ;
165
+ }
166
+ i += 1 ;
167
+ }
168
+
169
+ // With the sum of all input UDT tokens gathered, let's now iterate through
170
+ // output cells to grab the sum of all output UDT tokens.
171
+ uint128_t output_amount = 0 ;
172
+ i = 0 ;
173
+ while (1 ) {
174
+ uint128_t current_amount = 0 ;
175
+ len = 16 ;
176
+ // Similar to the above code piece, we are also looping through output cells
177
+ // with the same script as current running script here by using
178
+ // `CKB_SOURCE_GROUP_OUTPUT`.
179
+ ret = ckb_load_cell_data ((uint8_t * )& current_amount , & len , 0 , i ,
180
+ CKB_SOURCE_GROUP_OUTPUT );
181
+ if (ret == CKB_INDEX_OUT_OF_BOUND ) {
182
+ break ;
183
+ }
184
+ if (ret != CKB_SUCCESS ) {
185
+ return ret ;
186
+ }
187
+ if (len < 16 ) {
188
+ return ERROR_ENCODING ;
189
+ }
190
+ output_amount += current_amount ;
191
+ // Like any serious smart contract out there, we will need to check for
192
+ // overflows.
193
+ if (output_amount < current_amount ) {
194
+ return ERROR_OVERFLOWING ;
195
+ }
196
+ i += 1 ;
197
+ }
198
+
199
+ // When both value are gathered, we can perform the final check here to
200
+ // prevent non-authorized token issurance.
201
+ if (input_amount < output_amount ) {
202
+ return ERROR_AMOUNT ;
203
+ }
204
+ return CKB_SUCCESS ;
205
+ }
0 commit comments