FunC Cookbook
FunC Cookbook를 만든 핵심 이유는 FunC 개발자들의 모든 경험을 한 곳에 모아 미래의 개발자들이 이를 활용할 수 있도록 하기 위함입니다!
FunC Documentation과 비교했을 때, 이 문서는 FunC 개발자들이 스마트 컨트랙트 개발 중에 매일 해결하는 일상적인 작업에 더 초점을 맞추고 있습니다.
기초
if 문을 작성하는 방법
이벤트가 관련이 있는지 확인하고 싶다고 가정해봅시다. 이를 위해 플래그 변수를 사용합니다. FunC에서 true
는 -1
이고 false
는 0
임을 기억하세요.
int flag = 0; ;; false
if (flag) {
;; do something
}
else {
;; reject the transaction
}
💡 참고
0
이false
이므로 다른 모든 값은true
가 되기 때문에==
연산자가 필요하지 않습니다.
💡 유용한 링크
repeat 루프를 작성하는 방법
예시로 거듭제곱을 살펴보겠습니다.
int number = 2;
int multiplier = number;
int degree = 5;
repeat(degree - 1) {
number *= multiplier;
}
💡 유용한 링크
while 루프를 작성하는 방법
while은 특정 작업을 얼마나 자주 수행해야 하는지 모를 때 유용합니다. 예를 들어, 최대 4개의 다른 셀에 대한 참조를 저장할 수 있다고 알려진 cell
을 살펴보겠습니다.
cell inner_cell = begin_cell() ;; create a new empty builder
.store_uint(123, 16) ;; store uint with value 123 and length 16 bits
.end_cell(); ;; convert builder to a cell
cell message = begin_cell()
.store_ref(inner_cell) ;; store cell as reference
.store_ref(inner_cell)
.end_cell();
slice msg = message.begin_parse(); ;; convert cell to slice
while (msg.slice_refs_empty?() != -1) { ;; we should remind that -1 is true
cell inner_cell = msg~load_ref(); ;; load cell from slice msg
;; do something
}
💡 유용한 링크
do until 루프를 작성하는 방법
사이클이 최소한 한 번은 실행되어야 할 때 do until
을 사용합니다.
int flag = 0;
do {
;; do something even flag is false (0)
} until (flag == -1); ;; -1 is true
💡 유용한 링크
slice가 비어있는지 확인하는 방법
slice
로 작업하기 전에, 올바른 처리를 위해 데이터가 있는지 확인해야 합니다. 이를 위해 slice_empty?()
를 사용할 수 있지만, 최소 하나의 bit
데이터나 하나의 ref
가 있다면 0
(false
)을 반환한다는 점을 고려해야 합니다.
;; creating empty slice
slice empty_slice = "";
;; `slice_empty?()` returns `true`, because slice doesn't have any `bits` and `refs`
empty_slice.slice_empty?();
;; creating slice which contains bits only
slice slice_with_bits_only = "Hello, world!";
;; `slice_empty?()` returns `false`, because slice have any `bits`
slice_with_bits_only.slice_empty?();
;; creating slice which contains refs only
slice slice_with_refs_only = begin_cell()
.store_ref(null())
.end_cell()
.begin_parse();
;; `slice_empty?()` returns `false`, because slice have any `refs`
slice_with_refs_only.slice_empty?();
;; creating slice which contains bits and refs
slice slice_with_bits_and_refs = begin_cell()
.store_slice("Hello, world!")
.store_ref(null())
.end_cell()
.begin_parse();
;; `slice_empty?()` returns `false`, because slice have any `bits` and `refs`
slice_with_bits_and_refs.slice_empty?();
💡 유용한 링크
slice가 비어있는지 확인하는 방법 (refs는 있을 수 있지만 bits는 없는 경우)
slice
에서 bits
만 확인하고 refs
가 있는지는 중요하지 않다면, slice_data_empty?()
를 사용해야 합니다.
;; creating empty slice
slice empty_slice = "";
;; `slice_data_empty?()` returns `true`, because slice doesn't have any `bits`
empty_slice.slice_data_empty?();
;; creating slice which contains bits only
slice slice_with_bits_only = "Hello, world!";
;; `slice_data_empty?()` returns `false`, because slice have any `bits`
slice_with_bits_only.slice_data_empty?();
;; creating slice which contains refs only
slice slice_with_refs_only = begin_cell()
.store_ref(null())
.end_cell()
.begin_parse();
;; `slice_data_empty?()` returns `true`, because slice doesn't have any `bits`
slice_with_refs_only.slice_data_empty?();
;; creating slice which contains bits and refs
slice slice_with_bits_and_refs = begin_cell()
.store_slice("Hello, world!")
.store_ref(null())
.end_cell()
.begin_parse();
;; `slice_data_empty?()` returns `false`, because slice have any `bits`
slice_with_bits_and_refs.slice_data_empty?();
💡 유용한 링크
slice가 비어있는지 확인하는 방법 (bits는 있을 수 있지만 refs는 없는 경우)
refs
만 관심이 있다면, slice_refs_empty?()
를 사용하여 존재 여부를 확인해야 합니다.
;; creating empty slice
slice empty_slice = "";
;; `slice_refs_empty?()` returns `true`, because slice doesn't have any `refs`
empty_slice.slice_refs_empty?();
;; creating slice which contains bits only
slice slice_with_bits_only = "Hello, world!";
;; `slice_refs_empty?()` returns `true`, because slice doesn't have any `refs`
slice_with_bits_only.slice_refs_empty?();
;; creating slice which contains refs only
slice slice_with_refs_only = begin_cell()
.store_ref(null())
.end_cell()
.begin_parse();
;; `slice_refs_empty?()` returns `false`, because slice have any `refs`
slice_with_refs_only.slice_refs_empty?();
;; creating slice which contains bits and refs
slice slice_with_bits_and_refs = begin_cell()
.store_slice("Hello, world!")
.store_ref(null())
.end_cell()
.begin_parse();
;; `slice_refs_empty?()` returns `false`, because slice have any `refs`
slice_with_bits_and_refs.slice_refs_empty?();
💡 유용한 링크
cell이 비어있는지 확인하는 방법
cell
에 데이터가 있는지 확인하려면 먼저 slice
로 변환해야 합니다. bits
만 관심이 있다면 slice_data_empty?()
, refs
만 관심이 있다면 slice_refs_empty?()
를 사용해야 합니다. bit
나 ref
여부와 관계없이 데이터 존재 여부를 확인하려면 slice_empty?()
를 사용해야 합니다.
cell cell_with_bits_and_refs = begin_cell()
.store_uint(1337, 16)
.store_ref(null())
.end_cell();
;; Change `cell` type to slice with `begin_parse()`
slice cs = cell_with_bits_and_refs.begin_parse();
;; determine if slice is empty
if (cs.slice_empty?()) {
;; cell is empty
}
else {
;; cell is not empty
}
💡 유용한 링크
dict가 비어있는지 확인하는 방법
dict에 데이터가 있는지 확인하기 위한 dict_empty?()
메서드가 있습니다. 이 메서드는 보통 null
-cell이 빈 딕셔너리이기 때문에 cell_null?()
와 동일합니다.
cell d = new_dict();
d~udict_set(256, 0, "hello");
d~udict_set(256, 1, "world");
if (d.dict_empty?()) { ;; Determine if dict is empty
;; dict is empty
}
else {
;; dict is not empty
}
💡 유용한 링크
문서의 "new_dict()" - 빈 dict 생성
문서의 "dict_set()" - dict d에 함수로 요소를 추가하여 비어있지 않게 함
tuple이 비어있는지 확인하는 방법
tuple
로 작업할 때는 추출할 값이 있는지 항상 알아야 합니다. 빈 tuple
에서 값을 추출하려고 하면 "not a tuple of valid size"라는 exit code 7
오류가 발생합니다.
;; Declare tlen function because it's not presented in stdlib
(int) tlen (tuple t) asm "TLEN";
() main () {
tuple t = empty_tuple();
t~tpush(13);
t~tpush(37);
if (t.tlen() == 0) {
;; tuple is empty
}
else {
;; tuple is not empty
}
}
💡 참고
tlen 어셈블리 함수를 선언하고 있습니다. 자세한 내용은 여기에서, 모든 어셈블러 명령어 목록은 여기서 확인할 수 있습니다.
💡 유용한 링크
lisp-style 리스트가 비어있는지 확인하는 방법
tuple numbers = null();
numbers = cons(100, numbers);
if (numbers.null?()) {
;; list-style list is empty
} else {
;; list-style list is not empty
}
cons 함수를 사용하여 숫자 100을 리스트 스타일 리스트에 추가하므로 비어있지 않습니다.
스마트 컨트랙트의 상태가 비어있는지 확인하는 방법
거래 횟수를 저장하는 counter
가 있다고 가정해보겠습니다. 이 변수는 상태가 비어있기 때문에 스마트 컨트랙트의 첫 번째 거래 동안에는 사용할 수 없으므로, 이러한 경우를 처리해야 합니다. 상태가 비어있다면, counter
변수를 생성하고 저장합니다.
;; `get_data()` will return the data cell from contract state
cell contract_data = get_data();
slice cs = contract_data.begin_parse();
if (cs.slice_empty?()) {
;; contract data is empty, so we create counter and save it
int counter = 1;
;; create cell, add counter and save in contract state
set_data(begin_cell().store_uint(counter, 32).end_cell());
}
else {
;; contract data is not empty, so we get our counter, increase it and save
;; we should specify correct length of our counter in bits
int counter = cs~load_uint(32) + 1;
set_data(begin_cell().store_uint(counter, 32).end_cell());
}
💡 참고
cell이 비어있는지 확인하는 방법을 통해 컨트랙트의 상태가 비어있는지 확인할 수 있습니다.
💡 유용한 링크
내부 메시지 cell을 구성하는 방법
컨트랙트가 내부 메시지를 보내도록 하려면, 먼저 기술적인 플래그, 수신자 주소, 나머지 데이터를 지정하여 cell
로 적절하게 생성해야 합니다.
;; We use literal `a` to get valid address inside slice from string containing address
slice addr = "EQArzP5prfRJtDM5WrMNWyr9yUTAi0c9o6PfR4hkWy9UQXHx"a;
int amount = 1000000000;
;; we use `op` for identifying operations
int op = 0;
cell msg = begin_cell()
.store_uint(0x18, 6)
.store_slice(addr)
.store_coins(amount)
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) ;; default message headers (see sending messages page)
.store_uint(op, 32)
.end_cell();
send_raw_message(msg, 3); ;; mode 3 - pay fees separately and ignore errors
💡 참고
이 예시에서는 리터럴
a
를 사용하여 주소를 가져옵니다. 문자열 리터럴에 대해 자세히 알아보려면 문서를 참조하세요.
💡 참고
💡 유용한 링크
내부 메시지 cell에 ref로 본문을 포함하는 방법
플래그와 다른 기술적 데이터 다음에 오는 메시지 본문에서 int
, slice
, cell
을 보낼 수 있습니다. 후자의 경우, cell
이 계속될 것임을 나타내기 위해 store_ref()
전에 비트를 1
로 설정해야 합니다.
충분한 공간이 있다고 확신한다면 메시지 본문을 헤더와 같은 cell
내에 보낼 수도 있습니다. 이 경우 비트를 0
으로 설정해야 합니다.
;; We use literal `a` to get valid address inside slice from string containing address
slice addr = "EQArzP5prfRJtDM5WrMNWyr9yUTAi0c9o6PfR4hkWy9UQXHx"a;
int amount = 1000000000;
int op = 0;
cell message_body = begin_cell() ;; Creating a cell with message
.store_uint(op, 32)
.store_slice("❤")
.end_cell();
cell msg = begin_cell()
.store_uint(0x18, 6)
.store_slice(addr)
.store_coins(amount)
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1) ;; default message headers (see sending messages page)
.store_uint(1, 1) ;; set bit to 1 to indicate that the cell will go on
.store_ref(message_body)
.end_cell();
send_raw_message(msg, 3); ;; mode 3 - pay fees separately and ignore errors
💡 참고
이 예시에서는 리터럴
a
를 사용하여 주소를 가져옵니다. 문자열 리터럴에 대해 자세히 알아보려면 문서를 참조하세요.
💡 참고
이 예시에서는 mode 3을 사용하여 수신된 ton을 가져와서 지정된 만큼(amount) 정확히 보내면서 수수료는 컨트랙트 잔액에서 지불하고 오류를 무시합니다. Mode 64는 수수료를 제외한 모든 ton을 반환하는 데 필요하고, mode 128은 전체 잔액을 보냅니다.
💡 참고
메시지를 구성하고 있지만 메시지 본문은 별도로 추가합니다.
💡 유용한 링크
내부 메시지 cell에 slice로 본문을 포함하는 방법
메시지를 보낼 때 메시지 본문을 cell
이나 slice
로 보낼 수 있습니다. 이 예시에서는 메시지 본문을 slice
내에 보냅니다.
;; We use literal `a` to get valid address inside slice from string containing address
slice addr = "EQArzP5prfRJtDM5WrMNWyr9yUTAi0c9o6PfR4hkWy9UQXHx"a;
int amount = 1000000000;
int op = 0;
slice message_body = "❤";
cell msg = begin_cell()
.store_uint(0x18, 6)
.store_slice(addr)
.store_coins(amount)
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) ;; default message headers (see sending messages page)
.store_uint(op, 32)
.store_slice(message_body)
.end_cell();
send_raw_message(msg, 3); ;; mode 3 - pay fees separately and ignore errors
💡 참고
이 예시에서는 리터럴
a
를 사용하여 주소를 가져옵니다. 문자열 리터럴에 대해 자세히 알아보려면 문서를 참조하세요.
💡 참고
이 예시에서는 mode 3을 사용하여 수신된 ton을 가져와서 지정된 만큼(amount) 정확히 보내면서 수수료는 컨트랙트 잔액에서 지불하고 오류를 무시합니다. Mode 64는 수수료를 제외한 모든 ton을 반환하는 데 필요하고, mode 128은 전체 잔액을 보냅니다.
💡 참고
메시지를 구성하고 있지만 메시지를 slice로 추가합니다.
tuple을 순회하는 방법 (양방향)
FunC에서 배열이나 스택으로 작업하려면 tuple이 필요합니다. 그리고 무엇보다도 값들을 순회할 수 있어야 합니다.
(int) tlen (tuple t) asm "TLEN";
forall X -> (tuple) to_tuple (X x) asm "NOP";
() main () {
tuple t = to_tuple([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
int len = t.tlen();
int i = 0;
while (i < len) {
int x = t.at(i);
;; do something with x
i = i + 1;
}
i = len - 1;
while (i >= 0) {
int x = t.at(i);
;; do something with x
i = i - 1;
}
}
💡 참고
tlen
어셈블리 함수를 선언하고 있습니다. 자세한 내용은 여기에서, 모든 어셈블러 명령어 목록은 여기서 확인할 수 있습니다.또한
to_tuple
함수도 선언하고 있습니다. 이는 단순히 모든 입력의 데이터 타입을 tuple로 변경하므로 사용 시 주의해야 합니다.
asm
키워드를 사용하여 자체 함수 작성하는 방법
모든 기능을 사용할 때 실제로는 stdlib.fc
내에 미리 준비된 메서드를 사용합니다. 하지만 실제로는 더 많은 기회가 있으며, 이를 직접 작성하는 방법을 배워야 합니다.
예를 들어, tuple
에 요소를 추가하는 tpush
메서드는 있지만 tpop
은 없습니다. 이 경우 다음과 같이 해야 합니다:
;; ~ means it is modifying method
forall X -> (tuple, X) ~tpop (tuple t) asm "TPOP";
반복을 위해 tuple
의 길이를 알고 싶다면 TLEN
asm 명령어로 새 함수를 작성해야 합니다:
int tuple_length (tuple t) asm "TLEN";
stdlib.fc에서 이미 알고 있는 함수들의 몇 가지 예시:
slice begin_parse(cell c) asm "CTOS";
builder begin_cell() asm "NEWC";
cell end_cell(builder b) asm "ENDC";
💡 유용한 링크:
n중 중첩 tuple 순회하기
때로는 중첩된 tuple을 순회하고 싶을 수 있습니다. 다음 예시는 [[2,6],[1,[3,[3,5]]], 3]
형식의 tuple에서 헤드부터 시작하여 모든 항목을 순회하고 출력합니다.
int tuple_length (tuple t) asm "TLEN";
forall X -> (tuple, X) ~tpop (tuple t) asm "TPOP";
forall X -> int is_tuple (X x) asm "ISTUPLE";
forall X -> tuple cast_to_tuple (X x) asm "NOP";
forall X -> int cast_to_int (X x) asm "NOP";
forall X -> (tuple) to_tuple (X x) asm "NOP";
;; define global variable
global int max_value;
() iterate_tuple (tuple t) impure {
repeat (t.tuple_length()) {
var value = t~tpop();
if (is_tuple(value)) {
tuple tuple_value = cast_to_tuple(value);
iterate_tuple(tuple_value);
}
else {
if(value > max_value) {
max_value = value;
}
}
}
}
() main () {
tuple t = to_tuple([[2,6], [1, [3, [3, 5]]], 3]);
int len = t.tuple_length();
max_value = 0; ;; reset max_value;
iterate_tuple(t); ;; iterate tuple and find max value
~dump(max_value); ;; 6
}
💡 유용한 링크
tuple에서의 기본 연산
(int) tlen (tuple t) asm "TLEN";
forall X -> (tuple, X) ~tpop (tuple t) asm "TPOP";
() main () {
;; creating an empty tuple
tuple names = empty_tuple();
;; push new items
names~tpush("Naito Narihira");
names~tpush("Shiraki Shinichi");
names~tpush("Akamatsu Hachemon");
names~tpush("Takaki Yuichi");
;; pop last item
slice last_name = names~tpop();
;; get first item
slice first_name = names.first();
;; get an item by index
slice best_name = names.at(2);
;; getting the length of the list
int number_names = names.tlen();
}
X 타입 해결하기
다음 예시는 tuple에 어떤 값이 포함되어 있는지 확인하지만, tuple에는 X 타입(cell, slice, int, tuple, int)의 값이 포함되어 있습니다. 값을 확인하고 적절하게 캐스트해야 합니다.
forall X -> int is_null (X x) asm "ISNULL";
forall X -> int is_int (X x) asm "<{ TRY:<{ 0 PUSHINT ADD DROP -1 PUSHINT }>CATCH<{ 2DROP 0 PUSHINT }> }>CONT 1 1 CALLXARGS";
forall X -> int is_cell (X x) asm "<{ TRY:<{ CTOS DROP -1 PUSHINT }>CATCH<{ 2DROP 0 PUSHINT }> }>CONT 1 1 CALLXARGS";
forall X -> int is_slice (X x) asm "<{ TRY:<{ SBITS DROP -1 PUSHINT }>CATCH<{ 2DROP 0 PUSHINT }> }>CONT 1 1 CALLXARGS";
forall X -> int is_tuple (X x) asm "ISTUPLE";
forall X -> int cast_to_int (X x) asm "NOP";
forall X -> cell cast_to_cell (X x) asm "NOP";
forall X -> slice cast_to_slice (X x) asm "NOP";
forall X -> tuple cast_to_tuple (X x) asm "NOP";
forall X -> (tuple, X) ~tpop (tuple t) asm "TPOP";
forall X -> () resolve_type (X value) impure {
;; value here is of type X, since we dont know what is the exact value - we would need to check what is the value and then cast it
if (is_null(value)) {
;; do something with the null
}
elseif (is_int(value)) {
int valueAsInt = cast_to_int(value);
;; do something with the int
}
elseif (is_slice(value)) {
slice valueAsSlice = cast_to_slice(value);
;; do something with the slice
}
elseif (is_cell(value)) {
cell valueAsCell = cast_to_cell(value);
;; do something with the cell
}
elseif (is_tuple(value)) {
tuple valueAsTuple = cast_to_tuple(value);
;; do something with the tuple
}
}
() main () {
;; creating an empty tuple
tuple stack = empty_tuple();
;; let's say we have tuple and do not know the exact types of them
stack~tpush("Some text");
stack~tpush(4);
;; we use var because we do not know type of value
var value = stack~tpop();
resolve_type(value);
}
💡 유용한 링크
현재 시간 가져오기
int current_time = now();
if (current_time > 1672080143) {
;; do some stuff
}
난수 생성하기
자세한 내용은 난수 생성을 참조하세요.
randomize_lt(); ;; do this once
int a = rand(10);
int b = rand(1000000);
int c = random();
모듈로 연산
예를 들어 256개의 모든 숫자에 대해 (xp + zp)*(xp-zp)
계산을 수행한다고 가정해보겠습니다. 이러한 연산 대부분이 암호화에 사용되므로, 다음 예시에서는 몽고메리 곡선에 대한 모듈로 연산자를 사용합니다.
xp+zp는 유효한 변수 이름입니다(공백 없음).
(int) modulo_operations (int xp, int zp) {
;; 2^255 - 19 is a prime number for montgomery curves, meaning all operations should be done against its prime
int prime = 57896044618658097711785492504343953926634992332820282019728792003956564819949;
;; muldivmod handles the next two lines itself
;; int xp+zp = (xp + zp) % prime;
;; int xp-zp = (xp - zp + prime) % prime;
(_, int xp+zp*xp-zp) = muldivmod(xp + zp, xp - zp, prime);
return xp+zp*xp-zp;
}
💡 유용한 링크
오류를 발생시키는 방법
int number = 198;
throw_if(35, number > 50); ;; the error will be triggered only if the number is greater than 50
throw_unless(39, number == 198); ;; the error will be triggered only if the number is NOT EQUAL to 198
throw(36); ;; the error will be triggered anyway
tuple 뒤집기
tuple이 데이터를 스택으로 저장하기 때문에, 때로는 다른 쪽 끝에서 데이터를 읽기 위해 tuple을 뒤집어야 합니다.
forall X -> (tuple, X) ~tpop (tuple t) asm "TPOP";
int tuple_length (tuple t) asm "TLEN";
forall X -> (tuple) to_tuple (X x) asm "NOP";
(tuple) reverse_tuple (tuple t1) {
tuple t2 = empty_tuple();
repeat (t1.tuple_length()) {
var value = t1~tpop();
t2~tpush(value);
}
return t2;
}
() main () {
tuple t = to_tuple([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
tuple reversed_t = reverse_tuple(t);
~dump(reversed_t); ;; [10 9 8 7 6 5 4 3 2 1]
}
💡 유용한 링크
리스트에서 특정 인덱스의 항목을 제거하는 방법
int tlen (tuple t) asm "TLEN";
(tuple, ()) remove_item (tuple old_tuple, int place) {
tuple new_tuple = empty_tuple();
int i = 0;
while (i < old_tuple.tlen()) {
int el = old_tuple.at(i);
if (i != place) {
new_tuple~tpush(el);
}
i += 1;
}
return (new_tuple, ());
}
() main () {
tuple numbers = empty_tuple();
numbers~tpush(19);
numbers~tpush(999);
numbers~tpush(54);
~dump(numbers); ;; [19 999 54]
numbers~remove_item(1);
~dump(numbers); ;; [19 54]
}
slice가 동일한지 확인하는 방법
slice 해시를 기반으로 하는 방법과 SDEQ asm 명령어를 사용하는 방법, 두 가지 다른 방식으로 동일성을 확인할 수 있습니다.
int are_slices_equal_1? (slice a, slice b) {
return a.slice_hash() == b.slice_hash();
}
int are_slices_equal_2? (slice a, slice b) asm "SDEQ";
() main () {
slice a = "Some text";
slice b = "Some text";
~dump(are_slices_equal_1?(a, b)); ;; -1 = true
a = "Text";
;; We use literal `a` to get valid address inside slice from string containing address
b = "EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF"a;
~dump(are_slices_equal_2?(a, b)); ;; 0 = false
}
💡 유용한 링크
cell이 동일한지 확인하는 방법
cell의 해시를 기반으로 하여 쉽게 동일성을 확인할 수 있습니다.
int are_cells_equal? (cell a, cell b) {
return a.cell_hash() == b.cell_hash();
}
() main () {
cell a = begin_cell()
.store_uint(123, 16)
.end_cell();
cell b = begin_cell()
.store_uint(123, 16)
.end_cell();
~dump(are_cells_equal?(a, b)); ;; -1 = true
}
💡 유용한 링크
tuple이 동일한지 확인하는 방법
더 고급스러운 예시로는 tuple의 각 값을 순회하며 비교하는 것입니다. 값들이 X 타입이므로 해당하는 타입으로 확인하고 캐스트해야 하며, tuple인 경우 재귀적으로 순회해야 합니다.
int tuple_length (tuple t) asm "TLEN";
forall X -> (tuple, X) ~tpop (tuple t) asm "TPOP";
forall X -> int cast_to_int (X x) asm "NOP";
forall X -> cell cast_to_cell (X x) asm "NOP";
forall X -> slice cast_to_slice (X x) asm "NOP";
forall X -> tuple cast_to_tuple (X x) asm "NOP";
forall X -> int is_null (X x) asm "ISNULL";
forall X -> int is_int (X x) asm "<{ TRY:<{ 0 PUSHINT ADD DROP -1 PUSHINT }>CATCH<{ 2DROP 0 PUSHINT }> }>CONT 1 1 CALLXARGS";
forall X -> int is_cell (X x) asm "<{ TRY:<{ CTOS DROP -1 PUSHINT }>CATCH<{ 2DROP 0 PUSHINT }> }>CONT 1 1 CALLXARGS";
forall X -> int is_slice (X x) asm "<{ TRY:<{ SBITS DROP -1 PUSHINT }>CATCH<{ 2DROP 0 PUSHINT }> }>CONT 1 1 CALLXARGS";
forall X -> int is_tuple (X x) asm "ISTUPLE";
int are_slices_equal? (slice a, slice b) asm "SDEQ";
int are_cells_equal? (cell a, cell b) {
return a.cell_hash() == b.cell_hash();
}
(int) are_tuples_equal? (tuple t1, tuple t2) {
int equal? = -1; ;; initial value to true
if (t1.tuple_length() != t2.tuple_length()) {
;; if tuples are differ in length they cannot be equal
return 0;
}
int i = t1.tuple_length();
while (i > 0 & equal?) {
var v1 = t1~tpop();
var v2 = t2~tpop();
if (is_null(t1) & is_null(t2)) {
;; nulls are always equal
}
elseif (is_int(v1) & is_int(v2)) {
if (cast_to_int(v1) != cast_to_int(v2)) {
equal? = 0;
}
}
elseif (is_slice(v1) & is_slice(v2)) {
if (~ are_slices_equal?(cast_to_slice(v1), cast_to_slice(v2))) {
equal? = 0;
}
}
elseif (is_cell(v1) & is_cell(v2)) {
if (~ are_cells_equal?(cast_to_cell(v1), cast_to_cell(v2))) {
equal? = 0;
}
}
elseif (is_tuple(v1) & is_tuple(v2)) {
;; recursively determine nested tuples
if (~ are_tuples_equal?(cast_to_tuple(v1), cast_to_tuple(v2))) {
equal? = 0;
}
}
else {
equal? = 0;
}
i -= 1;
}
return equal?;
}
() main () {
tuple t1 = cast_to_tuple([[2, 6], [1, [3, [3, 5]]], 3]);
tuple t2 = cast_to_tuple([[2, 6], [1, [3, [3, 5]]], 3]);
~dump(are_tuples_equal?(t1, t2)); ;; -1
}
💡 유용한 링크
내부 주소 생성하기
새 컨트랙트를 배포해야 하지만 주소를 모를 때 내부 주소를 생성해야 합니다. 새 컨트랙트의 코드와 데이터인 state_init
이 이미 있다고 가정하겠습니다.
해당 MsgAddressInt TLB에 대한 내부 주소를 생성합니다.
(slice) generate_internal_address (int workchain_id, cell state_init) {
;; addr_std$10 anycast:(Maybe Anycast) workchain_id:int8 address:bits256 = MsgAddressInt;
return begin_cell()
.store_uint(2, 2) ;; addr_std$10
.store_uint(0, 1) ;; anycast nothing
.store_int(workchain_id, 8) ;; workchain_id: -1
.store_uint(cell_hash(state_init), 256)
.end_cell().begin_parse();
}
() main () {
slice deploy_address = generate_internal_address(workchain(), state_init);
;; then we can deploy new contract
}
💡 참고
이 예시에서는
workchain()
을 사용하여 workchain의 id를 가져옵니다. Workchain ID에 대해 자세히 알아보려면 문서를 참조하세요.
💡 유용한 링크