diff --git a/jetton_tests/.gitignore b/jetton_tests/.gitignore deleted file mode 100644 index 2053e73..0000000 --- a/jetton_tests/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.pk -build diff --git a/jetton_tests/README.md b/jetton_tests/README.md deleted file mode 100644 index b87aa56..0000000 --- a/jetton_tests/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# Jetton Minter example project - -This project allows you to: - -1. Build basic jetton minter contract -2. Aims to *hopefully* test any nftcollection contract for compliance with [Jetton standerd](https://github.com/ton-blockchain/TIPs/issues/74) -3. Deploy minter contract via `toncli deploy` -4. Manually deploy jetton wallet via minting tokens -5. Manually send to other jetton wallets -6. Manyally burn coins on your wallet - -## Building - - `toncli start jetton_minter` - `toncli build` - -## Testing - - Same here `toncli run_test` - If you encounter **error 6** during *run_tests* - make shure that your binaries are built according to:[this manual](https://github.com/disintar/toncli/blob/master/docs/advanced/func_tests_new.md) - -## Deploying minter contract - - This project consists of two sub-projects **jetton_minter** and **jetton_wallet** - You can see that in the *project.yml* - **BOTH** of those have to be built. - First type:`toncli build` - However it makes sense to deploy only *jetton_minter*. - Prior to deployment you need to check out *fift/minter_data.fif* - and change all mock configuration values to your own liking. - To deploy run:`toncli deploy -n testnet jetton_minter`. - -## Minting jettons - - To mint coins to your wallet - you will have to: - -+ Configure *fift/mint_jettons.fif* script with your own values: -[Take a look](https://github.com/ton-blockchain/TIPs/issues/74) - -+ Make yourself familiar with process of sending [internal messages](https://github.com/disintar/toncli/blob/master/docs/advanced/send_fift_internal.md) - -`toncli send -n testnet -a 0.035 -c jetton_minter --body fift/mint_jettons.fif` - -## Sending jettons - - To send coins to someone elses jetton wallet - you will have to: - -+ Setup values in *fift/send_jettons.fif* -+ Run:`toncli send -n testnet -a 0.1 --address < your jetton wallet addr> --body fift/send_jettons.fif` - -## Burning jettons - - To burn jettons - -+ Setup values in *fift/burn_jettons.fif* -+ Run `toncli send -n testnet -a 0.1 --address < your jetton wallet addr > --body fift/burn_jettons.fif` diff --git a/jetton_tests/fift/.gitkeep b/jetton_tests/fift/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/jetton_tests/fift/burn_jettons.fif b/jetton_tests/fift/burn_jettons.fif deleted file mode 100644 index bfd6c35..0000000 --- a/jetton_tests/fift/burn_jettons.fif +++ /dev/null @@ -1,8 +0,0 @@ -"TonUtil.fif" include - - diff --git a/jetton_tests/fift/mint_jettons.fif b/jetton_tests/fift/mint_jettons.fif deleted file mode 100644 index 6bbcdf2..0000000 --- a/jetton_tests/fift/mint_jettons.fif +++ /dev/null @@ -1,33 +0,0 @@ -"TonUtil.fif" include - -"EQDlT07NpSh0uj-aSBkF2TRxOqR2nw0ErOQsA6TYakr1-FxP" constant mint_address -0x178d4519 constant internal_transfer -1000000000 10 * constant mint_amount -30000000 constant forward_amount - -mint_address -$>smca 0= abort"Specify valid mint addr" -drop // Drop flags - -2constant mint_raw // worchain and addr into single constant - -// Here goes master message -// internal_transfer - - - - diff --git a/jetton_tests/fift/minter_data.fif b/jetton_tests/fift/minter_data.fif deleted file mode 100644 index 5382599..0000000 --- a/jetton_tests/fift/minter_data.fif +++ /dev/null @@ -1,28 +0,0 @@ -"TonUtil.fif" include -"Asm.fif" include - - -"EQDlT07NpSh0uj-aSBkF2TRxOqR2nw0ErOQsA6TYakr1-FxP" constant owner_address // Specify your own -"https://raw.githubusercontent.com/Trinketer22/token-contract/main/ft/web-example/test_jetton.json" constant jetton_meta // Specify your own -"build/jetton_wallet.fif" constant wallet_code_path -1000000000 100 * constant jetton_supply // Starting jetton supply - -B B, -b> - - -owner_address -$>smca 0= abort"Specify valid admin addr" -drop // Drop flags - - diff --git a/jetton_tests/fift/send_jettons.fif b/jetton_tests/fift/send_jettons.fif deleted file mode 100644 index 9574f61..0000000 --- a/jetton_tests/fift/send_jettons.fif +++ /dev/null @@ -1,27 +0,0 @@ -"TonUtil.fif" include - -"EQDRebAnF1pvH1YsKNp7mtpsz+CLs6WxaffUojt1ijyrazkg" constant receiver_address -1000000000 constant send_amount -20000000 constant forward_amount -12345 constant query_id -0xf8a7ea5 constant op_transfer - -receiver_address -$>smca 0= abort"Specify valid send addr" -drop // Drop flags - -2constant send_addr // worchain and addr into single constant - -// Here goes master message -// internal_transfer - - diff --git a/jetton_tests/fift/wallet_data.fif b/jetton_tests/fift/wallet_data.fif deleted file mode 100644 index 64abbf5..0000000 --- a/jetton_tests/fift/wallet_data.fif +++ /dev/null @@ -1,30 +0,0 @@ -"TonUtil.fif" include -"Asm.fif" include - -1000000000 10 * constant initial_balance // Wallet balance -"build/jetton_wallet.fif" constant wallet_code - -/* - This file is primerely for tests to run - Main way of deploying jetton wallet is via jetton minter "mint" operation. - Sending jettons from already deployed wallet is also an option - Thus random 256 bit value will be used ans and address for collection and owner - - We're going to use newkeypair as a way to generate two random 256 bit values - Do not do that in real app -*/ - -newkeypair - -256 B>u@ -swap -256 B>u@ - - diff --git a/jetton_tests/project.yaml b/jetton_tests/project.yaml deleted file mode 100644 index 65bfcd9..0000000 --- a/jetton_tests/project.yaml +++ /dev/null @@ -1,38 +0,0 @@ -jetton_minter: - data: fift/minter_data.fif - func: - - ../ft/op-codes.fc - - ../ft/params.fc - - ../ft/jetton-utils.fc - - ../ft/discovery-params.fc - - tests/utils/helpers.func - - ../ft/jetton-minter.fc - tests: - - tests/minter-tests.func - - tests/minter-tests-int.func - -jetton_discoverable: - data: fift/minter_data.fif - func: - - ../ft/op-codes.fc - - ../ft/params.fc - - tests/utils/helpers.func - - ../ft/jetton-utils.fc - - ../ft/discovery-params.fc - - ../ft/jetton-minter-discoverable.fc - tests: - - tests/minter-tests.func - - tests/minter-tests-int.func - - -jetton_wallet: - data: fift/wallet_data.fif - func: - - ../ft/op-codes.fc - - ../ft/params.fc - - ../ft/jetton-utils.fc - - tests/utils/helpers.func - - ../ft/jetton-wallet.fc - tests: - - tests/wallet-tests.func - - tests/wallet-tests-int.func diff --git a/jetton_tests/tests/.gitkeep b/jetton_tests/tests/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/jetton_tests/tests/minter-tests-int.func b/jetton_tests/tests/minter-tests-int.func deleted file mode 100644 index e957726..0000000 --- a/jetton_tests/tests/minter-tests-int.func +++ /dev/null @@ -1,96 +0,0 @@ -#pragma version >=0.2.0; - -#include "utils/constants.func"; - - -int __test_get_jetton_data_mint() { - - var( gas_before, stack ) = invoke_method( get_jetton_data, [] ); - - int total_supply = stack.first(); - int mintable? = stack.second(); - slice admin = stack.third(); - cell content = stack.fourth(); - cell code = stack.at( 4 ); - - - ;; Can't test non mintable contract with mint message. - if( ~ mintable? ) { - return gas_before; - } - - int query_id = rand(1337) + 1; - int forward_ton = one_unit / 10; - int mint_amount = ( rand( 100 ) + 1 ) * one_unit / 10; - slice rand_addr = generate_internal_address_with_custom_data(0, 0, random()); - slice mint_dst = generate_internal_address_with_custom_data(0, 0, random()); - slice mint_from = generate_empty_address(); - cell mint_payload = generate_jetton_internal_transfer_request( query_id, mint_amount, mint_from, rand_addr, forward_ton, null(), false ).end_cell(); - builder mint_body = generate_internal_message_body( op_mint, query_id ).store_slice( mint_dst ).store_grams( forward_ton ).store_ref( mint_payload ); - ;; Testing mint with non-admin address - cell msg = generate_internal_message_custom( 0, 0, 0, mint_body, admin, null(), 0 ); - - var( gas_mint, _ ) = invoke_method( recv_internal, [ one_unit, one_unit, msg, mint_body.end_cell().begin_parse() ] ); - var( gas_after, stack ) = invoke_method( get_jetton_data, [] ); - ;; Total supply should increase by our mint_amount - throw_unless( 500, total_supply + mint_amount == stack.first() ); - - ;; Rest should not change - - throw_unless( 501, mintable? == stack.second() ); - throw_unless( 502, equal_slices( admin, stack.third() ) ); - throw_unless( 503, equal_slices( content.begin_parse(), stack.fourth().begin_parse() ) ); - throw_unless( 504, equal_slices( code.begin_parse(), stack.at( 4 ).begin_parse() ) ); - - - return gas_before + gas_mint + gas_after; -} - -int __test_get_jetton_data_burn() { - - var( gas_before, stack ) = invoke_method( get_jetton_data, [] ); - - int total_supply = stack.first(); - int mintable? = stack.second(); - slice admin = stack.third(); - cell content = stack.fourth(); - cell code = stack.at( 4 ); - - ;;Nothing to burn - if( total_supply == 0 ) { - return gas_before; - } - - {- - If jetton is not mintabe, does it mean that it's necesserely - non-burnable too? IDK. - I'd say doesn't and burnable? flag required. - So we would ignore mintable? in this test. - -} - - int query_id = rand(1337) + 1; - int forward_ton = one_unit / 10; - int burn_amount = ( rand( 100 ) + 1 ) * one_unit / 10; - - slice jetton_addr = my_address(); ;;In testing mode my_address() for test code equals my_address() for contract code - slice sender = generate_internal_address_with_custom_data( 0, 0, random() ); - slice src = calculate_user_jetton_wallet_address( sender, jetton_addr, code ); - var msg_body = generate_jetton_burn_notification( query_id, burn_amount, sender, admin ); - cell msg = generate_internal_message_custom( 0, 0, 0, msg_body, src, null(), 0); - - ( int gas_burn, _ ) = invoke_method( recv_internal, [one_unit, one_unit, msg, msg_body.end_cell().begin_parse() ] ); - - ( int gas_after, stack ) = invoke_method( get_jetton_data, [] ); - - - throw_unless( 500, total_supply - burn_amount == stack.first() ); - - ;; Rest should not change - - throw_unless( 501, mintable? == stack.second() ); - throw_unless( 502, equal_slices( admin, stack.third() ) ); - throw_unless( 503, equal_slices( content.begin_parse(), stack.fourth().begin_parse() ) ); - throw_unless( 504, equal_slices( code.begin_parse(), stack.at( 4 ).begin_parse() ) ); - - return gas_before + gas_burn + gas_after; -} diff --git a/jetton_tests/tests/minter-tests.func b/jetton_tests/tests/minter-tests.func deleted file mode 100644 index a08651c..0000000 --- a/jetton_tests/tests/minter-tests.func +++ /dev/null @@ -1,204 +0,0 @@ -#pragma version >=0.2.0; - -#include "utils/constants.func"; -#include "utils/op-codes.fc"; - -(int, slice, cell, cell) load_test_data() inline { - slice ds = get_data().begin_parse(); - return ( - ds~load_coins(), ;; total_supply - ds~load_msg_addr(), ;; admin_address - ds~load_ref(), ;; content - ds~load_ref() ;; jetton_wallet_code - ); -} - -_ verify_excess_jetton( int query_id, slice resp_dst, int msg_value, int forward_fee, int forward_amount, cell msg ) impure inline { - {- - TL-B schema: excesses#d53276db query_id:uint64 = InternalMsgBody; - Excess message should be sent to resp_dst with all of the msg_value - fees taken to process - We verify that: - 1) message is sent to resp_dst - 2) attached amount is at least msg_value - forward_fee * 2 - 3) op matches excess op - 4) query_id matches request query_id - -} - - tuple parsed_msg = unsafe_tuple( parse_internal_message( msg ) ); - - ;;Check dst_addr to be equal to resp_dst - throw_unless( 701, equal_slices( resp_dst, parsed_msg.at( 4 ) ) ); - - int total_sent = parsed_msg.at( 5 ); - int should_sent = msg_value - forward_amount - forward_fee * 2; - - throw_unless( 702, total_sent >= should_sent ); - - slice msg_body = parsed_msg.at( 8 ); - - throw_unless( 703, op_excesses == msg_body~load_uint( 32 ) ); - - throw_unless( 704, query_id == msg_body~load_uint( 64 ) ); -} - -_ validate_TIP_64( slice content_data ) impure inline { - - int content_layout = content_data~load_uint( 8 ); - - ;; Check for allowed content_layout - throw_unless( 305, ( content_layout == 1 ) | ( content_layout == 0 ) ); - - if( content_layout == 1 ){ - - ;; Check that off-chain URI contains at least one ASCII char - throw_unless( 306, token_snake_len( content_data ) > 8 ); - } else { - ;; On-chain is stored as dict - ;; Has to be non-empty - throw_if( 306, content_data.preload_dict().dict_empty?() ); - - ;; Perhaps could go further and test for Optional dict keys but none of those are required so i'll leave it be - ;; For now - } -} - -int __test_burn_notification() { - {- - burn_notification query_id:uint64 amount:(VarUInteger 16) - sender:MsgAddress response_destination:MsgAddress - = InternalMsgBody; - - On receiving burn notification jetton master should - 1) Decrease total supply by burn_amount. - 2) Send excess message to the response_destination. - - -} - var ( total_supply, admin_address, content, code ) = load_test_data(); - - int query_id = rand( 1337 ) + 1; - int burn_amount = total_supply / 10; - ;; Making sure that source address would be valid jetton address - - slice sender = generate_internal_address_with_custom_data( 0, 0, random() ); - slice src = calculate_user_jetton_wallet_address( sender, my_address(), code ); - var msg_body = generate_jetton_burn_notification( query_id, burn_amount, sender, admin_address ); - cell msg = generate_internal_message_custom( 0, 0, 0, msg_body, src, null(), 0); - - ( int gas_used, _ ) = invoke_method( recv_internal, [one_unit, one_unit, msg, msg_body.end_cell().begin_parse() ] ); - - ;; Expect single message - tuple actions = parse_c5(); - throw_unless( 600, actions.tuple_length() == 1 ); - - ( int action_type, cell sent_msg, int mode ) = actions.at(0).untriple(); - throw_unless( 601, action_type == 0 ); - throw_unless( 602, ( mode >= 64 ) & ( mode <= 66 ) ); - - ;; it's mode 64 so msg_value - fees return guaranteed - ;; Still have to verify excess structure - - verify_excess_jetton( query_id, admin_address, 0, 0, 0, sent_msg ); - - var( new_supply, _, _, _ ) = load_test_data(); - - throw_unless( 603, total_supply - burn_amount == new_supply ); - - - return gas_used; -} - -int __test_mint() { - - ;;Non-standardized but widely used message - var ( total_supply, admin_address, content, code ) = load_test_data(); - - int query_id = rand(1337) + 1; - int forward_ton = one_unit / 10; - int mint_amount = ( rand( 100 ) + 1 ) * one_unit / 10; - slice rand_addr = generate_internal_address_with_custom_data(0, 0, random()); - slice mint_dst = generate_internal_address_with_custom_data(0, 0, random()); - slice mint_from = generate_empty_address(); - - {- - In current implementation sends any forward payload provided. - However for jettons to be successfully creditated on wallet balance - it should have internal_transfer message format. - -} - - cell mint_payload = generate_jetton_internal_transfer_request( query_id, mint_amount, mint_from, rand_addr, forward_ton, null(), false ).end_cell(); - builder mint_body = generate_internal_message_body( op_mint, query_id ).store_slice( mint_dst ).store_grams( forward_ton ).store_ref( mint_payload ); - ;; Testing mint with non-admin address - cell msg = generate_internal_message_custom( 0, 0, 0, mint_body, rand_addr, null(), 0 ); - - int gas_failed = invoke_method_expect_fail( recv_internal, [ one_unit, one_unit, msg, mint_body.end_cell().begin_parse() ] ); - assert_no_actions(); - - ;;Now changing source address to admin - msg = generate_internal_message_custom( 0, 0, 0, mint_body, admin_address, null(), 0 ); - - var( gas_success, _ ) = invoke_method( recv_internal, [ one_unit, one_unit, msg, mint_body.end_cell().begin_parse() ] ); - - ;; Expect single message - tuple actions = parse_c5(); - throw_unless( 600, actions.tuple_length() == 1 ); - - ( int action_type, cell sent_msg, int mode ) = actions.at(0).untriple(); - throw_unless( 601, action_type == 0 ); - - tuple parsed_msg = unsafe_tuple( parse_internal_message( sent_msg ) ); - - {- - What do we know about how mint message should look like in general? - 1) Wallet has to be deployed thus StateInit has to be present - 2) Our forward payload should be the message body - That's what we're going to check for. - Rest is very contract specific. - -} - - throw_if( 602, parsed_msg.at( 7 ).null?() ); - throw_unless( 603, equal_slices( mint_payload.begin_parse(), parsed_msg.at( 8 ) ) ); - - ;; Also total supply should increase by mint_amount - - var( new_supply, _, _, _ ) = load_test_data(); - throw_unless( 604, total_supply + mint_amount == new_supply ); - - return gas_failed + gas_success; - -} - -;; Get methods tests start here - -int __test_get_jetton_data() { - - int expect_mintable? = true; ;;Determines if test expects mintable contract - - var ( total_supply, admin_address, content, code ) = load_test_data(); - - var ( gas_used, stack ) = invoke_method( get_jetton_data, [] ); - - throw_unless( 500, stack.tuple_length() == 5 ); - - throw_unless( 501, total_supply == stack.first() ); - throw_unless( 502, expect_mintable? & stack.second() ); - throw_unless( 503, equal_slices( admin_address, stack.third() ) ); - - slice res_cs = stack.fourth().begin_parse(); ;; Content slice - - throw_unless( 504, equal_slices( content.begin_parse(), res_cs ) ); - validate_TIP_64( res_cs ); - throw_unless( 505, equal_slices( code.begin_parse(), stack.at( 4 ).begin_parse() ) ); - - return gas_used; -} - -int __test_get_wallet_address() { - - var ( gas_used, stack ) = invoke_method( get_wallet_address, [ my_address() ] ); - - throw_unless( 700, stack.tuple_length() == 1 ); - - parse_std_addr( stack.first() ); ;;I guess that's what else we can check - - return gas_used; -} diff --git a/jetton_tests/tests/utils/constants.func b/jetton_tests/tests/utils/constants.func deleted file mode 100644 index 28221d7..0000000 --- a/jetton_tests/tests/utils/constants.func +++ /dev/null @@ -1,7 +0,0 @@ -const int one_unit = 1000000000; ;; 10^9 - -;; These are not standardized values each Jetton can have it's own fee guidelines -const int jetton_min_storage = 10000000; ;;0.01 TON -const int jetton_gas_fee = 10000000; ;;0.01 TON - - diff --git a/jetton_tests/tests/utils/helpers.func b/jetton_tests/tests/utils/helpers.func deleted file mode 100644 index 8cf2360..0000000 --- a/jetton_tests/tests/utils/helpers.func +++ /dev/null @@ -1,2 +0,0 @@ -int equal_slices (slice a, slice b) asm "SDEQ"; -int tuple_length( tuple t ) asm "TLEN"; diff --git a/jetton_tests/tests/utils/op-codes.fc b/jetton_tests/tests/utils/op-codes.fc deleted file mode 100644 index 2b6ff80..0000000 --- a/jetton_tests/tests/utils/op-codes.fc +++ /dev/null @@ -1,9 +0,0 @@ -const int op_transfer = 0xf8a7ea5; -const int op_transfer_notification = 0x7362d09c; -const int op_internal_transfer = 0x178d4519; -const int op_excesses = 0xd53276db; -const int op_burn = 0x595f07bc; -const int op_burn_notification = 0x7bdd97de; - -;; Minter -const int op_mint = 21; diff --git a/jetton_tests/tests/wallet-tests-int.func b/jetton_tests/tests/wallet-tests-int.func deleted file mode 100644 index 1e509c7..0000000 --- a/jetton_tests/tests/wallet-tests-int.func +++ /dev/null @@ -1,118 +0,0 @@ -#pragma version >=0.2.0; - -#include "utils/constants.func"; - - -int __test_get_wallet_data_transfer() { - - var ( _, dst, resp_dst, query_id ) = setup_req_fields(); - var ( gas_before, stack ) = invoke_method( get_wallet_data, [] ); - - int balance = stack.first(); - slice owner = stack.second(); - slice master = stack.third(); - cell code = stack.fourth(); - - ;; Nothing to send - - if( balance <= 0 ) { - return gas_before; ;;throw( 100 ) ? - } - - int transfer_amount = ( rand( 10 ) + 1 ) * balance / 10; ;; From 0.1 * balance to 1 * balance - int forward_amount = one_unit / 10; - int forward_fee = one_unit / 100; - - - builder msg_body = generate_jetton_transfer_request( query_id, transfer_amount, dst,resp_dst, null(), forward_amount, null(), false ); - cell msg = generate_internal_message_custom( 0, 0, 0, msg_body, owner, null(), forward_fee ); - - var ( gas_transfer, _ ) = invoke_method( recv_internal, [ one_unit, one_unit, msg, msg_body.end_cell().begin_parse() ] ); - var ( gas_after, stack ) = invoke_method( get_wallet_data, [] ); - - int new_balance = stack.first(); - - throw_unless( 500, balance - transfer_amount == stack.first() ); - - ;; Rest should not change - throw_unless( 501, equal_slices( owner, stack.second() ) ); - throw_unless( 502, equal_slices( master, stack.third() ) ); - throw_unless( 503, equal_slices( code.begin_parse(), stack.fourth().begin_parse() ) ); - - - return gas_before + gas_transfer + gas_after; -} - -int __test_get_wallet_internal_transfer() { - - {- - When incoming transfer happens - Balance should increase accordingly - -} - - var ( _, dst, resp_dst, query_id ) = setup_req_fields(); - var ( gas_before, stack ) = invoke_method( get_wallet_data, [] ); - - int balance = stack.first(); - slice owner = stack.second(); - slice master = stack.third(); - cell code = stack.fourth(); - - - int transfer_amount = ( rand( 10 ) + 1 ) * one_unit / 10; ;;From 0.1 to 1 unit - int forward_ton = one_unit / 10; - int msg_value = one_unit; - - var msg_body = generate_jetton_internal_transfer_request( query_id, transfer_amount, master, dst, forward_ton, null(), false ); - cell msg = generate_internal_message_custom( 0, 0, 0, msg_body, master, null(), 0 ); - - var ( gas_transfer, _ ) = invoke_method( recv_internal, [ msg_value, msg_value, msg, msg_body.end_cell().begin_parse() ] ); - var ( gas_after, stack ) = invoke_method( get_wallet_data, [] ); - - throw_unless( 500, balance + transfer_amount == stack.first() ); - ;; Rest should not change - throw_unless( 501, equal_slices( owner, stack.second() ) ); - throw_unless( 502, equal_slices( master, stack.third() ) ); - throw_unless( 503, equal_slices( code.begin_parse(), stack.fourth().begin_parse() ) ); - - return gas_before + gas_transfer + gas_after; -} - -int __test_get_wallet_data_burn() { - - var ( _, dst, resp_dst, query_id ) = setup_req_fields(); - var ( gas_before, stack ) = invoke_method( get_wallet_data, [] ); - - int balance = stack.first(); - slice owner = stack.second(); - slice master = stack.third(); - cell code = stack.fourth(); - - - ;; Nothing to burn - - if( balance <= 0 ) { - return gas_before; ;;throw( 100 ) ? - } - - int burn_amount = ( rand( 10 ) + 1 ) * balance / 10; ;; From 0.1 * balance to 1 * balance - int forward_amount = one_unit / 10; - int forward_fee = one_unit / 100; - var msg_body = generate_jetton_burn_request( query_id, burn_amount, resp_dst, null() ); - cell msg = generate_internal_message_custom( 0, 0, 0, msg_body, owner, null(), 0 ); - - var ( gas_burn, _ ) = invoke_method( recv_internal, [ one_unit, one_unit, msg, msg_body.end_cell().begin_parse() ] ); - - var ( gas_after, stack ) = invoke_method( get_wallet_data, [] ); - - throw_unless( 500, balance - burn_amount == stack.first() ); - - ;; Rest should not change - throw_unless( 501, equal_slices( owner, stack.second() ) ); - throw_unless( 502, equal_slices( master, stack.third() ) ); - throw_unless( 503, equal_slices( code.begin_parse(), stack.fourth().begin_parse() ) ); - - - - return gas_before + gas_burn; -} diff --git a/jetton_tests/tests/wallet-tests.func b/jetton_tests/tests/wallet-tests.func deleted file mode 100644 index 17fce59..0000000 --- a/jetton_tests/tests/wallet-tests.func +++ /dev/null @@ -1,666 +0,0 @@ -#pragma version >=0.2.0; - -#include "utils/constants.func"; -#include "utils/op-codes.fc"; - - -( int, slice, slice, cell ) load_test_data() inline { - - slice ds = get_data().begin_parse(); - return (ds~load_coins(), ds~load_msg_addr(), ds~load_msg_addr(), ds~load_ref()); -} - -( slice ) get_owner() inline { - - slice ds = get_data().begin_parse(); - ds~load_coins(); - - return ds~load_msg_addr(); -} - -( slice ) get_master() inline { - slice ds = get_data().begin_parse(); - ds~load_coins(); - ds~load_msg_addr(); - return ds~load_msg_addr(); -} - -( int ) get_test_balance() inline { - slice ds = get_data().begin_parse(); - - return ds~load_coins(); -} - -( cell,() ) replace_msg_source( cell msg, slice new_addr ) inline { - slice old_msg = msg.begin_parse(); - builder new_msg = begin_cell().store_slice( old_msg~load_bits( 4 ) ); - old_msg~load_msg_addr(); ;;Skip source addr - new_msg = new_msg.store_slice( new_addr ).store_slice( old_msg ); ;;Store new addr and the rest of the orig msg - - return ( new_msg.end_cell(),() ); - -} - -slice gen_non_owner( slice old_owner ) inline { - slice new_owner = generate_internal_address_with_custom_data( 0, 0, random() ); - - ;; In theory we can win a bingo and get same 256bit integer from RNG - while( equal_slices( new_owner, old_owner ) ) { - new_owner = generate_internal_address_with_custom_data( 0, 0, random() ); - } - - return new_owner; -} - -( slice, slice, slice, int ) setup_req_fields() { - slice owner = get_owner(); - slice dst = generate_internal_address_with_custom_data( 0, 0, random() ); - slice resp_dst = generate_internal_address_with_custom_data( 0, 0, random() ); - - return ( owner, dst, resp_dst, rand( 1337 ) + 1 ); -} - -cell get_state_init_field( tuple state_init, int idx ) inline { - cell res = null(); - ;; Next flag index - int next_idx = 0; - - do { - int flag = state_init.at( next_idx ); - - next_idx = flag ? next_idx + 2 : next_idx + 1; - idx -= 1; - - if( idx == 0 & flag ) { - res = state_init.at( next_idx - 1 ); - } - - } until( ~ res.null?() | idx <= 0 ) - - return res; -} - -_ verify_transfer_notification( cell msg, int jetton_amount, int query_id, slice dst, slice resp_dst, slice sender, int forward_amount, cell payload ) impure inline { - {- - transfer_notification#7362d09c query_id:uint64 amount:(VarUInteger 16) - sender:MsgAddress forward_payload:(Either Cell ^Cell) - = InternalMsgBody; - - Transfer notification message is sent when forward_amount > 0 from receiver to sender wallet - 1) query_id should be equal to the query_id of sender request - 2) jetton_amount should be equal to the amount of transfered jettons - 3) sender should be equal to the sender address - 4) forward_payload ( if any ) should be equal to the payload of sender request - -} - - - tuple parsed_msg = unsafe_tuple( parse_internal_message( msg ) ); - - {- - Why it's dst and ton resp_dst? - I don't fully get it, but by standard: - if forward_amount > 0 ensure that receiver's jetton-wallet send message to destination address with forward_amount nanotons attached - Probably has something to do with separating excess and forward amount value - -} - throw_unless( 801, equal_slices( dst, parsed_msg.at( 4 ) ) ); - - ;;forward_amount tons should be attached - throw_unless( 802, forward_amount == parsed_msg.at( 5 ) ); - - slice msg_body = parsed_msg.at( 8 ); - - throw_unless( 803, op_transfer_notification == msg_body~load_uint( 32 ) ); - - throw_unless( 804, query_id == msg_body~load_uint( 64 ) ); - - throw_unless( 805, jetton_amount == msg_body~load_coins() ); - - throw_unless( 806, equal_slices( sender, msg_body~load_msg_addr() ) ); - - if( ~ null?( payload ) ) { - if( msg_body~load_uint( 1 ) ) { - throw_unless( 807, equal_slices( payload.begin_parse(), msg_body~load_ref().begin_parse() ) ); - } - else { - throw_unless( 807, equal_slices( payload.begin_parse(), msg_body ) ); - } - } -} - -_ verify_burn_notification( cell msg, int query_id, int burn_amount, slice sender, slice resp_dst ) impure inline { - {- - burn_notification query_id:uint64 amount:(VarUInteger 16) - sender:MsgAddress response_destination:MsgAddress - = InternalMsgBody; - - Burn notification is sent to master jetton( minter ) contract on successfull burn - - 1) Message dst addr should be equal to master jetton addr - 2) query_id should be equal to request query_id - 3) amount should be equal to request burn_amount - 4) response_destination should be equal to request resp_dst - - -} - - tuple parsed_msg = unsafe_tuple( parse_internal_message( msg ) ); - - ;; Burn notification should be sent to master - throw_unless( 901, equal_slices( get_master(), parsed_msg.at( 4 ) ) ); - - slice msg_body = parsed_msg.at( 8 ); - - throw_unless( 902, op_burn_notification == msg_body~load_uint( 32 ) ); - - throw_unless( 903, query_id == msg_body~load_uint( 64 ) ); - - throw_unless( 903, burn_amount == msg_body~load_coins() ); - - throw_unless( 904, equal_slices( sender, msg_body~load_msg_addr() ) ); - - throw_unless( 905, equal_slices( resp_dst, msg_body~load_msg_addr() ) ); -} - -_ verify_excess_jetton( int query_id, slice resp_dst, int msg_value, int forward_fee, int forward_amount, cell msg ) impure inline { - {- - TL-B schema: excesses#d53276db query_id:uint64 = InternalMsgBody; - Excess message should be sent to resp_dst with all of the msg_value - fees taken to process - We verify that: - 1) message is sent to resp_dst - 2) attached amount is at least msg_value - forward_fee * 2 - 3) op matches excess op - 4) query_id matches request query_id - -} - - tuple parsed_msg = unsafe_tuple( parse_internal_message( msg ) ); - - ;;Check dst_addr to be equal to resp_dst - throw_unless( 701, equal_slices( resp_dst, parsed_msg.at( 4 ) ) ); - - int total_sent = parsed_msg.at( 5 ); - int should_sent = msg_value - forward_amount - forward_fee * 2; - - throw_unless( 702, total_sent >= should_sent ); - - slice msg_body = parsed_msg.at( 8 ); - - throw_unless( 703, op_excesses == msg_body~load_uint( 32 ) ); - - throw_unless( 704, query_id == msg_body~load_uint( 64 ) ); -} - -int transfer_test_msg_value( int msg_value, int forward_fee, int forward_amount, int expect_fail? ) impure inline { - {- - Transfer should be rejected if wallet has less or equal to - forward_amount + number of forwarded messages ( 1 if no forward_amount 2 otherwise ) * fwd_fee + jetton_gas_fee + jetton_min_storage - on the balance. - Tricky part is that forward and response part happens on the receiver wallet. - So jetton_gas_fee should be taken twice by sender and the receiver + one min_storage on the receiver part - Thus IMHO at least one jetton_gas_fee should be accounted from sender balance and not msg_value. - However author if this contract comparse all of that vs msg_value that would be passed to the receiver - https://github.com/ton-blockchain/token-contract/blob/main/ft/jetton-wallet.fc#L80 - -} - - var ( owner, dst, resp_dst, query_id ) = setup_req_fields(); - - int gas_used = 0; - int transfer_amount = get_test_balance(); - builder msg_body = generate_jetton_transfer_request( query_id, transfer_amount, dst,resp_dst, null(), forward_amount, null(), false ); - cell msg = generate_internal_message_custom( 0, 0, 0, msg_body, owner, null(), forward_fee ); - - if( expect_fail? ) { - ;; Should fail with no actions - gas_used = invoke_method_expect_fail( recv_internal, [ one_unit, msg_value, msg, msg_body.end_cell().begin_parse() ] ); - assert_no_actions(); - } - else { - - var ( gas_used, _ ) = invoke_method( recv_internal, [one_unit, msg_value + jetton_min_storage, msg, msg_body.end_cell().begin_parse() ] ); - } - - return gas_used; -} - -{- - Testing wallet transfer query handling - - transfer#0f8a7ea5 query_id:uint64 amount:(VarUInteger 16) destination:MsgAddress - response_destination:MsgAddress custom_payload:(Maybe ^Cell) - forward_ton_amount:(VarUInteger 16) forward_payload:(Either Cell ^Cell) - = InternalMsgBody; - --} - -int __test_transfer_not_owner() { - - var ( owner, dst, resp_dst, query_id ) = setup_req_fields(); - - slice non_owner = gen_non_owner( owner ); - int query_id = rand( 1337 ) + 1; - builder msg_body = generate_jetton_transfer_request( query_id, one_unit, dst,resp_dst, null(), 0, null(), false ); - cell msg = generate_internal_message_custom( 0, 0, 0, msg_body, non_owner, null(), 0 ); - - ;; Should fail with no actions - int gas_err = invoke_method_expect_fail( recv_internal, [ one_unit * 10, one_unit, msg, msg_body.end_cell().begin_parse() ] ); - assert_no_actions(); - - ;;Now verify that owner addr triggered fail by changing source to owner addr - - msg = generate_internal_message_custom( 0, 0, 0, msg_body, owner, null(), 0 ); - - var ( gas_success, _ ) = invoke_method( recv_internal, [ one_unit * 10, one_unit, msg, msg_body.end_cell().begin_parse() ] ); - - return gas_err + gas_success; -} - -int __test_transfer_no_jettons() { - - var ( owner, dst, resp_dst, query_id ) = setup_req_fields(); - - int balance = get_test_balance(); - int transfer_amount = balance * 10; ;; Going to ask for way more jettons - - - builder msg_body = generate_jetton_transfer_request( query_id, transfer_amount, dst,resp_dst, null(), 0, null(), false ); - cell msg = generate_internal_message_custom( 0, 0, 0, msg_body, owner, null(), 0 ); - - ;; Should fail with no actions - int gas_err = invoke_method_expect_fail( recv_internal, [ one_unit * 10, one_unit, msg, msg_body.end_cell().begin_parse() ] ); - assert_no_actions(); - - ;; Now let's verify that balance triggers fail by requesting transfer equal to balance amount - - msg_body = generate_jetton_transfer_request( query_id, balance, dst,resp_dst, null(), 0, null(), false ); - - msg = generate_internal_message_custom( 0, 0, 0, msg_body, owner, null(), 0 ); - - var ( gas_success, _ ) = invoke_method( recv_internal, [ one_unit * 10, one_unit, msg, msg_body.end_cell().begin_parse() ] ); - - return gas_err + gas_success; -} - -int __test_transfer_storage_fee() { - int transfer_fee = jetton_gas_fee * 2; - int forward_fee = one_unit / 10; - int forward_amount = one_unit / 4; - int excesses = 1; - int msg_expected = forward_amount > 0 ? 2 : 1; - int msg_value = transfer_fee + muldiv(forward_fee, 3, 2) * msg_expected + forward_amount + excesses; ;;Account for everythin except storage fee - int gas_used = 0; - - ;;Expect fail - gas_used += transfer_test_msg_value( msg_value, forward_fee, forward_amount, true ); - - ;; Verifying error trigger - gas_used += transfer_test_msg_value( msg_value + jetton_min_storage, forward_fee, forward_amount, false ) ; - - return gas_used; -} - - -int __test_transfer_jetton_gas_fee() { - int transfer_fee = jetton_gas_fee * 2; - int forward_fee = one_unit / 10; - int forward_amount = one_unit / 4; - int excesses = 1; - int msg_expected = forward_amount > 0 ? 2 : 1; - int msg_value = muldiv(forward_fee, 3, 2) * msg_expected + forward_amount + excesses; ;;Account for everythin except transfer gas fee - int gas_used = 0; - - ;;Expect fail - gas_used += transfer_test_msg_value( msg_value, forward_fee, forward_amount, true ); - - ;; Verifying error trigger - gas_used += transfer_test_msg_value( msg_value + transfer_fee, forward_fee, forward_amount, false ) ; - - return gas_used; -} - -int __test_transfer_forward_fee_once() { - int transfer_fee = jetton_gas_fee * 2; - int forward_fee = one_unit / 10; - int forward_amount = 0; ;;one_unit / 4; - int excesses = 1; - int msg_expected = forward_amount > 0 ? 2 : 1; - int msg_value = transfer_fee + forward_amount + excesses; ;;Account for everythin except forward_fee; - int gas_used = 0; - - ;;Expect fail - gas_used += transfer_test_msg_value( msg_value, forward_fee, forward_amount, true ); - - ;; Verifying error trigger - gas_used += transfer_test_msg_value( msg_value + muldiv(forward_fee, 3, 2), forward_fee, forward_amount, false ) ; - - return gas_used; -} - -int __test_transfer_forward_fee_twice() { - int transfer_fee = jetton_gas_fee * 2; - int forward_fee = one_unit / 10; - int forward_amount = one_unit / 4; - int excesses = 1; - int msg_expected = forward_amount > 0 ? 2 : 1; - int msg_value = transfer_fee + muldiv(forward_fee, 3, 2) + forward_amount + excesses; ;;Account for everythin except that two forward fees instead of one - int gas_used = 0; - - ;;Expect fail - gas_used += transfer_test_msg_value( msg_value, forward_fee, forward_amount, true ); - - ;; Verifying error trigger - gas_used += transfer_test_msg_value( msg_value + muldiv(forward_fee, 3, 2), forward_fee, forward_amount, false ) ; - - return gas_used; -} - -int __test_transfer_forward_amount() { - int transfer_fee = jetton_gas_fee * 2; - int forward_fee = one_unit / 10; - int forward_amount = one_unit / 4; - int excesses = 1; - int msg_expected = forward_amount > 0 ? 2 : 1; - int msg_value = transfer_fee + muldiv(forward_fee, 3, 2) * msg_expected + excesses; ;;Account for everythin except forward_amount - int gas_used = 0; - - ;;Expect fail - gas_used += transfer_test_msg_value( msg_value, forward_fee, forward_amount, true ); - - ;; Verifying error trigger - gas_used += transfer_test_msg_value( msg_value + forward_amount, forward_fee, forward_amount, false ) ; - - return gas_used; -} - -int __test_transfer_excess() { - int transfer_fee = jetton_gas_fee * 2; - int forward_fee = one_unit / 10; - int forward_amount = one_unit / 4; - int excesses = 1; - int msg_expected = forward_amount > 0 ? 2 : 1; - int msg_value = transfer_fee + muldiv(forward_fee, 3, 2) * msg_expected + forward_amount; ;;Account for everythin except excess ( nothing to return ) - int gas_used = 0; - - ;;Expect fail - gas_used += transfer_test_msg_value( msg_value, forward_fee, forward_amount, true ); - - ;; Verifying error trigger - gas_used += transfer_test_msg_value( msg_value + excesses, forward_fee, forward_amount, false ) ; - - return gas_used; -} - -int verify_successfull_transfer( int transfer_amount, int forward_amount, cell fwd_payload ) impure inline { - - var ( owner, dst, resp_dst, query_id ) = setup_req_fields(); - - int forward_fee = one_unit / 10; - int msg_value = one_unit * 2; - int prev_balance = get_test_balance(); - int expect_messages = forward_amount > 0 ? 2 : 1; - - builder msg_body = generate_jetton_transfer_request( query_id, transfer_amount, dst,resp_dst, null(), forward_amount, fwd_payload, false ); - cell msg = generate_internal_message_custom( 0, 0, 0, msg_body, owner, null(), forward_fee ); - - ;; Should fail with no actions - var ( gas_send, _ ) = invoke_method( recv_internal, [ one_unit, msg_value, msg, msg_body.end_cell().begin_parse() ] ); - - ;; Here we should get a message sent. - - tuple actions = parse_c5(); - - throw_unless( 500, actions.tuple_length() == 1 ); - - ( int action_type, cell sent_msg, int mode ) = actions.at(0).untriple(); - - throw_unless( 501, action_type == 0 ); - ;; And we should verify that wallet balance decreased - - throw_unless( 502, prev_balance > get_test_balance() ); - - {- - Now here comes the tricky part. - We should verify that receiver wallet would send certain messages back to dst and resp_dst - At a first glace receiver wallet is deployed from this contract code so we can just relay those messages - to the recv_internal of this contract. - However, data in c4 is different from one deployed contract to another. - So let's try to pick data from StateInit and set it to this contract c4 to make sure that it's on the same page - with hypothetical receiver. - -} - - cell old_data = get_data(); ;; Backup old c4 data - - - tuple parsed_msg = unsafe_tuple( parse_internal_message( sent_msg ) ); - tuple state_init = parsed_msg.at( 7 ); - throw_if( 503, null?( state_init ) ); - - ;;Fourth element of StateInit is data cell - - cell sent_data = state_init.get_state_init_field( 4 ); - throw_if( 504, null?( sent_data ) ); - - set_data( sent_data ); ;;Now we got same persistent state the receiver would in real world - slice sent_body = parsed_msg.at( 8 ); ;;Getting parsed msg body - - {- - Now we're going to have issues with the source address of the message because in tesing mode it is not going to pass - calculate_user_jetton_wallet_address check - So let's switch it up with the jetton master address - Expect successfull execution - and up to two messages. - 1) if forward_amount > 0 also transfer notification - - transfer_notification#7362d09c query_id:uint64 amount:(VarUInteger 16) - sender:MsgAddress forward_payload:(Either Cell ^Cell) - = InternalMsgBody; - - 2) excesses#d53276db query_id:uint64 = InternalMsgBody; - -} - - sent_msg~replace_msg_source( get_master() ); - ;;Let's say balance was empty before - var ( gas_replay, _ ) = invoke_method( recv_internal, [ msg_value - forward_fee, msg_value - forward_fee, sent_msg, sent_body ] ); - actions = parse_c5(); - throw_unless( 505, actions.tuple_length() == expect_messages ); - - if( forward_amount > 0 ) { - - ( action_type, cell notify_msg, mode ) = actions.at(0).untriple(); - throw_unless( 501, action_type == 0 ); - - verify_transfer_notification( notify_msg, transfer_amount, query_id, dst, resp_dst, owner, forward_amount, fwd_payload ); - } - - ( action_type, cell excess_msg, mode ) = actions.at( expect_messages - 1 ).untriple(); - - throw_unless( 501, action_type == 0 ); - - verify_excess_jetton( query_id, resp_dst, msg_value - forward_fee, forward_fee, forward_amount, excess_msg ); - - return gas_send + gas_replay; -} - -int __test_transfer_success_no_fwd() { - int gas_used = verify_successfull_transfer( one_unit, 0, null() ); ;;Test without forward amount first - - return gas_used; -} - -int __test_transfer_success_with_fwd() { - - cell payload = begin_cell().store_slice("Hop hey").end_cell(); - ;; 0.25 forward amount and some payload - int gas_used = verify_successfull_transfer( one_unit, one_unit / 4, payload ); ;;Test with forward amount - return gas_used; -} - -{- - Testing burn request - - burn#595f07bc query_id:uint64 amount:(VarUInteger 16) - response_destination:MsgAddress custom_payload:(Maybe ^Cell) - = InternalMsgBody; - --} - -int burn_test_msg_value( int msg_value, int forward_fee, int expect_fail? ) impure inline { - ;; Testing msg_value related cases - var ( owner, dst, resp_dst, query_id ) = setup_req_fields(); - int gas_used = 0; - var msg_body = generate_jetton_burn_request( query_id, get_test_balance() / 10, resp_dst, null() ); - cell msg = generate_internal_message_custom( 0, 0, 0, msg_body, owner, null(), forward_fee ); - slice ms = msg_body.end_cell().begin_parse(); - - if( expect_fail? ) { - gas_used = invoke_method_expect_fail( recv_internal, [ msg_value, msg_value, msg, ms ] ); - } - else { - (gas_used, _ ) = invoke_method( recv_internal, [ msg_value, msg_value, msg, ms ] ); - } - - return gas_used; -} - -int __test_burn_not_owner() { - - var ( owner, dst, resp_dst, query_id ) = setup_req_fields(); - - slice non_owner = gen_non_owner( owner ); - var msg_body = generate_jetton_burn_request( query_id, one_unit * 5, resp_dst, null() ); ;;Burn 5 jetton units - cell msg = generate_internal_message_custom( 0, 0, 0, msg_body, non_owner, null(), 0 ); - int msg_value = one_unit; - - ;; Should fail with no actions - int gas_err = invoke_method_expect_fail( recv_internal, [ one_unit, one_unit, msg, msg_body.end_cell().begin_parse() ] ); - assert_no_actions(); - - ;; changing msg source addres to owner to verify error trigger - msg = generate_internal_message_custom( 0, 0, 0, msg_body, owner, null(), 0 ); - - var ( gas_success, _ ) = invoke_method( recv_internal, [ one_unit, one_unit, msg, msg_body.end_cell().begin_parse() ] ); - - return gas_err + gas_success; -} - -int __test_burn_too_many() { - ;; Testing case where user burns more jettons than available on balance - var ( owner, dst, resp_dst, query_id ) = setup_req_fields(); - - int balance = get_test_balance(); - var msg_body = generate_jetton_burn_request( query_id, balance + 1, resp_dst, null() ); ;;Burn balance + 1 jettons - cell msg = generate_internal_message_custom( 0, 0, 0, msg_body, owner, null(), 0 ); - - ;; Should fail with no actions - int gas_err = invoke_method_expect_fail( recv_internal, [ one_unit, one_unit, msg, msg_body.end_cell().begin_parse() ] ); - assert_no_actions(); - - ;; Now transfer exactly balance to verify error trigger - msg_body = generate_jetton_burn_request( query_id, balance, resp_dst, null() ); - - var ( gas_success, _ ) = invoke_method( recv_internal, [ one_unit, one_unit, msg, msg_body.end_cell().begin_parse() ] ); - - return gas_err + gas_success; -} - -int __test_burn_no_gas() { - int burn_fee = jetton_gas_fee * 2; - int forward_fee = one_unit / 10; - int excess = 1; - int msg_value = muldiv(forward_fee, 3, 2) + excess; ;;Only forward fee and access no gas fee - int gas_used = burn_test_msg_value( msg_value, forward_fee, true ); - assert_no_actions(); - ;; verifying error trigger - gas_used += burn_test_msg_value( msg_value + burn_fee, forward_fee, false ); - - return gas_used; - -} - -int __test_burn_no_forward_fee() { - int burn_fee = jetton_gas_fee * 2; - int forward_fee = one_unit / 10; - int excess = 1; - int msg_value = burn_fee + excess; ;;Only burn_fee and excess no forward_fee - int gas_used = burn_test_msg_value( msg_value, forward_fee, true ); - assert_no_actions(); - ;; verifying error trigger - gas_used += burn_test_msg_value( msg_value + muldiv(forward_fee, 3, 2), forward_fee, false ); - - return gas_used; -} - -int __test_burn_no_excess() { - int burn_fee = jetton_gas_fee * 2; - int forward_fee = one_unit / 10; - int excess = 1; - int msg_value = burn_fee + muldiv(forward_fee, 3, 2); ;;Only burn_fee + forward_fee nothing to return back - int gas_used = burn_test_msg_value( msg_value, forward_fee, true ); - assert_no_actions(); - ;; verifying error trigger - gas_used += burn_test_msg_value( msg_value + excess, forward_fee, false ); - - return gas_used; -} - -int __test_burn_successfull() { - var ( owner, dst, resp_dst, query_id ) = setup_req_fields(); - - int balance = get_test_balance(); - int msg_value = one_unit; - int forward_fee = one_unit / 10; - int forward_amount = one_unit / 4; - int burn_amount = ( rand( 10 ) + 1 ) * balance / 10; - - var msg_body = generate_jetton_burn_request( query_id, burn_amount, resp_dst, null() ); - cell msg = generate_internal_message_custom( 0, 0, 0, msg_body, owner, null(), 0 ); - var ( gas_used, _ ) = invoke_method( recv_internal, [ msg_value, msg_value, msg, msg_body.end_cell().begin_parse() ] ); - - ;; Balance should decrease by burn amount - throw_unless( 502, balance - burn_amount == get_test_balance() ); - - ;; We expect excess message to be sent to resp_dst - tuple actions = parse_c5(); - - throw_unless( 505, actions.tuple_length() == 1 ); - - ( int action_type, cell burn_note, int mode ) = actions.first().untriple(); - throw_unless( 501, action_type == 0 ); - - verify_burn_notification( burn_note, query_id, burn_amount, owner, resp_dst ); - - return gas_used; - -} - -int __test_internal_transfer() { - - var ( owner, dst, resp_dst, query_id ) = setup_req_fields(); - - slice from = get_master(); - int balance = get_test_balance(); - int forward_ton = one_unit / 10; - int msg_value = one_unit; - int transfer_amount = ( rand( 10 ) + 1 ) * one_unit / 10; ;;From 0.1 to 1 unit - - - var msg_body = generate_jetton_internal_transfer_request( query_id, transfer_amount, from, dst, forward_ton, null(), false ); - cell msg = generate_internal_message_custom( 0, 0, 0, msg_body, from, null(), 0 ); - var ( gas_used, _ ) = invoke_method( recv_internal, [ msg_value, msg_value, msg, msg_body.end_cell().begin_parse() ] ); - - throw_unless( 500, balance + transfer_amount == get_test_balance() ); - - return gas_used; -} - -;; Get methods testing starts here - -int __test_get_wallet_data() { - var ( balance, owner, master, code ) = load_test_data(); - var ( gas_used, stack ) = invoke_method( get_wallet_data, [] ); - - throw_unless( 300, stack.tuple_length() == 4 ); - throw_unless( 301, balance == stack.first() ); - throw_unless( 302, equal_slices( owner, stack.second() ) ); - throw_unless( 303, equal_slices( master, stack.third() ) ); - throw_unless( 304, equal_slices( code.begin_parse(), stack.fourth().begin_parse() ) ); - - return gas_used; -} diff --git a/nft_tests/.gitignore b/nft_tests/.gitignore deleted file mode 100644 index 2053e73..0000000 --- a/nft_tests/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.pk -build diff --git a/nft_tests/README.md b/nft_tests/README.md deleted file mode 100644 index 27efa70..0000000 --- a/nft_tests/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# NFT Collection example project - -This project allows you to: - -1. Build basic nft collection contract -2. Aims to *hopefully* test any nft collection contract for compliance with [NFT Standard](https://github.com/ton-blockchain/TIPs/issues/62) -3. Deploy collection contract via `toncli deploy nft_collection` -4. Manually deploy NFT item to the collection look (Deploying individual items) - -## Building - - Just run `toncli build` - Depending on your fift/func build you may want - to uncomment some *func/helpers* - -## Testing - - Build project and then: `toncli run_test` - - ⚠ If you see `6` error code on all tests - you need to update your binary [more information here](https://github.com/disintar/toncli/issues/72) - -## Deploying collection contract - - This project consists of two subprojects **nft_item** and **nft_collection** - You can see that in the *project.yml* - **BOTH** of those have to be built. - However, it makes sense to deploy only *nft_collection*. - Prior to deployment you need to check out *fift/collection-data.fif* - and change all mock configuration values like collection_content, - owner_address Etc. - To deploy run:`toncli deploy -n testnet nft_collection`. - -## Deploying individual items - - To deploy your own NFT item to the already deployed collection - you will need: - -+ Configure *fift/deploy.fif* script with your own values: -[Take a look](https://github.com/ton-blockchain/TIPs/issues/64) - -+ Make yourself familiar with process of sending [internal messages](https://github.com/disintar/toncli/blob/master/docs/advanced/send_fift_internal.md) - -`toncli send -n testnet -a 0.05 -c nft_collection --body fift/deploy.fif` -Every next item deployment you should make sure to -change item index in the *fift/deploy.fif* file ( Yes. Manually for now ). - -## Parse nft content - -Parse nft for collection (will work only if collection-data is same with on-chain): - -`toncli get get_nft_data -a "NFT_ADDRESS" --fift ./fift/parse-data-nft-collection.fif` - -Parse nft for single: - -`toncli get get_nft_data -a "NFT_ADDRESS" --fift ./fift/parse-data-nft-single.fif` diff --git a/nft_tests/fift/.gitkeep b/nft_tests/fift/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/nft_tests/fift/collection-data.fif b/nft_tests/fift/collection-data.fif deleted file mode 100644 index 8037470..0000000 --- a/nft_tests/fift/collection-data.fif +++ /dev/null @@ -1,68 +0,0 @@ -"TonUtil.fif" include -"Color.fif" include -"Asm.fif" include - -// toncli creates file in fift-libs -// where store deploy wallet address -// you can use this address to control nft or nft collection -"OwnerAddr.fif" include - -// "EQDlT07NpSh0uj-aSBkF2TRxOqR2nw0ErOQsA6TYakr1-FxP" instead of owner_address <- you can use address in any form -// or use address of toncli deploy wallet: -owner_address constant collection_owner_address - -// Change to your own -"EQB8EqvPWlUq6g3pZPW9AQU4xLDDh65MxfFT_UStljYuCy_X" constant royalty_address - -// 5 / 100 = 5% :) -5 constant royalty_numerator -100 constant royalty_denominator - -// Specify your own collection base uri -"https://raw.githubusercontent.com/Trinketer22/token-contract/main/nft/web-example/" constant collection_base - -// Specify your own collection metadata uri -"https://raw.githubusercontent.com/Trinketer22/token-contract/main/nft/web-example/my_collection.json" constant collection_json - - -// Path to builded NFT smart contract -"build/nft.fif" constant nft_path - -// Parse and save addresses - -royalty_address $>smca 0= abort"Specify valid royalty addr" drop -2constant royalty_raw - -collection_owner_address $>smca 0= abort"Specify valid owner addr" drop -2constant owner_raw - -^reset ."ðŸ‘― Owner Address: " owner_address ^green type cr -^reset ."ðŸ‘ŧ Royalty Address: " royalty_address ^green type cr -^reset - -nft_path include constant nft_code - -// just little helper -{ B B, b> } : offchain-token-data - - -// This is final c4 of smart contract -// It will be loaded in nft-collection.func in 11 line - -B B, b> ref, - b> ref, // content cell - - nft_code ref, // nft code - - ref, // royalty cell -b> diff --git a/nft_tests/fift/deploy.fif b/nft_tests/fift/deploy.fif deleted file mode 100644 index bdde92f..0000000 --- a/nft_tests/fift/deploy.fif +++ /dev/null @@ -1,27 +0,0 @@ -"TonUtil.fif" include - -50000000 =: storage_grams - -// "EQDlT07NpSh0uj-aSBkF2TRxOqR2nw0ErOQsA6TYakr1-FxP" -"TotallyNotValid" constant nft_owner_address - -."Deploying with nft owner address:" nft_owner_address type cr -nft_owner_address $>smca 0= abort"Specify valid owner address" -drop // Dropping address flags - - ref, // nft content -b> constant nft_payload - - - storage_grams Gram, // Storing forward_amount for nft deployment - // Initialization message body consists of owner_address and ref to the nft_content cell - nft_payload ref, // Storing content ref -b> - - - diff --git a/nft_tests/fift/nft-data.fif b/nft_tests/fift/nft-data.fif deleted file mode 100644 index 1280606..0000000 --- a/nft_tests/fift/nft-data.fif +++ /dev/null @@ -1,39 +0,0 @@ -"TonUtil.fif" include -"Asm.fif" include - -"kQDzy14OkWffhTvu289ftEY0xFZCqNU1cUbpiv8rZvQviL5F" constant collection_address // Specify your own -"EQDlT07NpSh0uj-aSBkF2TRxOqR2nw0ErOQsA6TYakr1-FxP" constant owner_address // Specify your own -"my_nft.json" constant nft_json // Your nft metadata relative path to collection_base - - - -collection_address -$>smca 0= abort"Specify valid collection address" -drop - -2constant coll_raw - -."Collection address:" collection_address type cr - -owner_address -$>smca 0= abort"Specify valid owner address" -drop - -2constant owner_raw - -."Owner address:" owner_address type cr - - - -B B, -b> - - diff --git a/nft_tests/fift/parse-data-nft-collection.fif b/nft_tests/fift/parse-data-nft-collection.fif deleted file mode 100644 index db804a5..0000000 --- a/nft_tests/fift/parse-data-nft-collection.fif +++ /dev/null @@ -1,60 +0,0 @@ -"TonUtil.fif" include -"Color.fif" include - -// In stack: init?, index, collection address, owner_address, body -4 roll // In stack: index, collection address, owner_address, body, init? -^reset ."👋 NFT is inited: " ^magenta (dump) type cr - -3 roll // In stack: collection address, owner_address, body, index -constant index -^reset ."ðŸ‘ŋ NFT index: " ^green index (dump) type cr - - -^reset 2 roll addr@ ."ðŸĪ— Collection address: " ^yellow print-addr cr // In stack: owner_address, body, collection address SLICE -constant nft-body -^reset ."ðŸĪŊ NFT Body: " nft-body $ (dump) type cr - - -// Parse data with collection - -"../build/nft_collection.fif" include constant collection-code -"./collection-data.fif" include constant collection-data - -0x076ef1ea // magic -0 // actions -0 // msgs_sent -0 // unixtime -1 // block_lt -0 // trans_lt -239 // randseed -1000000000 null pair // balance_remaining - $ ^cyan (dump) type cr - -.s -^reset ."😏 NFT individual content: " ref@ $ ^green (dump) type cr - -^reset ."ðŸū NFT data: " $ ^cyan (dump) type cr diff --git a/nft_tests/fift/parse-data-nft-single.fif b/nft_tests/fift/parse-data-nft-single.fif deleted file mode 100644 index cf0bbba..0000000 --- a/nft_tests/fift/parse-data-nft-single.fif +++ /dev/null @@ -1,22 +0,0 @@ -"TonUtil.fif" include -"Color.fif" include - -// In stack: init?, index, collection address, owner_address, body -4 roll // In stack: index, collection address, owner_address, body, init? -^reset ."👋 NFT is inited: " ^magenta (dump) type cr - -3 roll // In stack: collection address, owner_address, body, index -constant index -^reset ."ðŸ‘ŋ NFT index: " ^green index (dump) type cr - - -^reset 2 roll addr@ ."ðŸĪ— Collection address: " ^yellow print-addr cr // In stack: owner_address, body, collection address SLICE -constant nft-body -^reset ."ðŸĪŊ NFT Body: " nft-body $ (dump) type cr - diff --git a/nft_tests/func/utils/helpers.func b/nft_tests/func/utils/helpers.func deleted file mode 100644 index 2dd37bc..0000000 --- a/nft_tests/func/utils/helpers.func +++ /dev/null @@ -1,222 +0,0 @@ -;; Standard library for funC -;; - -;;forall X -> tuple cons(X head, tuple tail) asm "CONS"; -;;forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; -;;forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; -;;forall X -> X car(tuple list) asm "CAR"; -;;tuple cdr(tuple list) asm "CDR"; -;;tuple empty_tuple() asm "NIL"; -;;forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; -;;forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; -;;forall X -> [X] single(X x) asm "SINGLE"; -;;forall X -> X unsingle([X] t) asm "UNSINGLE"; -;;forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; -;;forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; -;;forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; -;;forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; -;;forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; -;;forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; -;;forall X -> X first(tuple t) asm "FIRST"; -;;forall X -> X second(tuple t) asm "SECOND"; -;;forall X -> X third(tuple t) asm "THIRD"; -;;forall X -> X fourth(tuple t) asm "3 INDEX"; -;;forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; -;;forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; -;;forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; -;;forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; -;;forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; -;;forall X -> X null() asm "PUSHNULL"; -;;forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; -;; -;;int now() asm "NOW"; -;;slice my_address() asm "MYADDR"; -;;[int, cell] get_balance() asm "BALANCE"; -;;int cur_lt() asm "LTIME"; -;;int block_lt() asm "BLOCKLT"; -;; -;;int cell_hash(cell c) asm "HASHCU"; -;;int slice_hash(slice s) asm "HASHSU"; -;;int string_hash(slice s) asm "SHA256U"; -;; -;;int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; -;;int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; -;; -;;(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; -;;(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; -;;(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; -;;(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; -;; -;;;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; -;; -;;() dump_stack() impure asm "DUMPSTK"; -;; -;;cell get_data() asm "c4 PUSH"; -;;() set_data(cell c) impure asm "c4 POP"; -;;cont get_c3() impure asm "c3 PUSH"; -;;() set_c3(cont c) impure asm "c3 POP"; -;;cont bless(slice s) impure asm "BLESS"; -;; -;;() accept_message() impure asm "ACCEPT"; -;;() set_gas_limit(int limit) impure asm "SETGASLIMIT"; -;;() commit() impure asm "COMMIT"; -;;() buy_gas(int gram) impure asm "BUYGAS"; -;; -;;int min(int x, int y) asm "MIN"; -;;int max(int x, int y) asm "MAX"; -;;(int, int) minmax(int x, int y) asm "MINMAX"; -;;int abs(int x) asm "ABS"; -;; -;;slice begin_parse(cell c) asm "CTOS"; -;;() end_parse(slice s) impure asm "ENDS"; -;;(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; -;;cell preload_ref(slice s) asm "PLDREF"; -;;;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; -;;;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; -;;;; int preload_int(slice s, int len) asm "PLDIX"; -;;;; int preload_uint(slice s, int len) asm "PLDUX"; -;;;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; -;;;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; -;;(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; -;;slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; -;;(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; -;;slice first_bits(slice s, int len) asm "SDCUTFIRST"; -;;slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; -;;(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; -;;slice slice_last(slice s, int len) asm "SDCUTLAST"; -;;(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; -;;cell preload_dict(slice s) asm "PLDDICT"; -;;slice skip_dict(slice s) asm "SKIPDICT"; -;; -;;(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; -;;cell preload_maybe_ref(slice s) asm "PLDOPTREF"; -;;builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; -;; -;;int cell_depth(cell c) asm "CDEPTH"; -;; -;;int slice_refs(slice s) asm "SREFS"; -;;int slice_bits(slice s) asm "SBITS"; -;;(int, int) slice_bits_refs(slice s) asm "SBITREFS"; -;;int slice_empty?(slice s) asm "SEMPTY"; -;;int slice_data_empty?(slice s) asm "SDEMPTY"; -;;int slice_refs_empty?(slice s) asm "SREMPTY"; -;;int slice_depth(slice s) asm "SDEPTH"; -;; -;;int builder_refs(builder b) asm "BREFS"; -;;int builder_bits(builder b) asm "BBITS"; -;;int builder_depth(builder b) asm "BDEPTH"; -;; -;;builder begin_cell() asm "NEWC"; -;;cell end_cell(builder b) asm "ENDC"; -;;builder store_ref(builder b, cell c) asm(c b) "STREF"; -;;;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; -;;;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; -;;builder store_slice(builder b, slice s) asm "STSLICER"; -;;builder store_grams(builder b, int x) asm "STGRAMS"; -;;builder store_dict(builder b, cell c) asm(c b) "STDICT"; -;; -;;(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; -;;tuple parse_addr(slice s) asm "PARSEMSGADDR"; -;;(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; -;;(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; -;; -;;cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; -;;(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; -;;cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; -;;(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; -;;cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; -;;(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF"; -;;(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF"; -;;(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; -;;(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; -;;(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; -;;(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; -;;(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; -;;(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; -;;(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; -;;(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; -;;(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; -;;(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; -;;cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; -;;(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; -;;cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; -;;(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; -;;cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; -;;(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; -;;(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; -;;(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; -;;(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; -;;(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; -;;cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; -;;(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; -;;cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; -;;(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; -;;cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; -;;(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; -;;(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; -;;(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; -;;(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; -;;(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; -;;(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; -;;(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; -;;(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; -;;(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; -;;(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; -;;(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; -;;(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; -;;(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; -;;(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; -;;(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; -;;(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; -;;(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; -;;(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; -;;(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; -;;(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; -;;(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; -;;(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; -;;(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; -;;(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; -;;(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; -;;(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; -;;(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; -;;(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; -;;(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; -;;(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; -;;(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; -;;(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; -;;(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; -;;cell new_dict() asm "NEWDICT"; -;;int dict_empty?(cell c) asm "DICTEMPTY"; -;; -;;(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; -;;(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; -;;(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; - -;;cell config_param(int x) asm "CONFIGOPTPARAM"; -;;int cell_null?(cell c) asm "ISNULL"; -;; -;;() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; -;;() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; -;;() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; -;;() set_code(cell new_code) impure asm "SETCODE"; -;; -;;int random() impure asm "RANDU256"; -;;int rand(int range) impure asm "RAND"; -;;int get_seed() impure asm "RANDSEED"; -;;int set_seed() impure asm "SETRAND"; -;;() randomize(int x) impure asm "ADDRAND"; -;;() randomize_lt() impure asm "LTIME" "ADDRAND"; -;; -;;builder store_coins(builder b, int x) asm "STVARUINT16"; -;;(slice, int) load_coins(slice s) asm( -> 1 0) "LDVARUINT16"; - -int equal_slices (slice a, slice b) asm "SDEQ"; -int builder_null?(builder b) asm "ISNULL"; -int tuple_length( tuple t ) asm "TLEN"; -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"; -;;builder store_builder(builder to, builder from) asm "STBR"; - diff --git a/nft_tests/func/utils/op-codes.func b/nft_tests/func/utils/op-codes.func deleted file mode 100644 index b3f8be8..0000000 --- a/nft_tests/func/utils/op-codes.func +++ /dev/null @@ -1,12 +0,0 @@ -const int op_transfer = 0x5fcc3d14; -const int op_ownership_assigned = 0x05138d91; -const int op_excesses = 0xd53276db; -const int op_get_static_data = 0x2fcb26a2; -const int op_report_static_data = 0x8b771735; -const int op_get_royalty_params = 0x693d3950; -const int op_report_royalty_params = 0xa8cb00ad; - -;; NFTEditable -const int op_edit_content = 0x1a0b9d51; -const int op_transfer_editorship = 0x1c04412a; -const int op_editorship_assigned = 0x511a4463; diff --git a/nft_tests/func/utils/params.func b/nft_tests/func/utils/params.func deleted file mode 100644 index eaaaa27..0000000 --- a/nft_tests/func/utils/params.func +++ /dev/null @@ -1,6 +0,0 @@ -const int workchain = 0; - -() force_chain(slice addr) impure { - (int wc, _) = parse_std_addr(addr); - throw_unless(333, wc == workchain); -} diff --git a/nft_tests/project.yaml b/nft_tests/project.yaml deleted file mode 100644 index 16f8d1f..0000000 --- a/nft_tests/project.yaml +++ /dev/null @@ -1,21 +0,0 @@ -nft_collection: - data: fift/collection-data.fif - func: - - func/utils/helpers.func - - ../nft//params.fc - - ../nft//op-codes.fc - - ../nft//nft-collection.fc - tests: - - tests/collection-tests.func - - tests/collection-tests-int.func - -nft: - data: fift/nft-data.fif - func: - - func/utils/helpers.func - - ../nft/params.fc - - ../nft/op-codes.fc - - ../nft/nft-item.fc - tests: - - tests/nft-tests.func - - tests/nft-tests-int.func diff --git a/nft_tests/tests/.gitkeep b/nft_tests/tests/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/nft_tests/tests/collection-tests-int.func b/nft_tests/tests/collection-tests-int.func deleted file mode 100644 index c6727c7..0000000 --- a/nft_tests/tests/collection-tests-int.func +++ /dev/null @@ -1,94 +0,0 @@ -#pragma version >=0.2.0; - -#include "utils/collection-data.func"; -#include "utils/constants.func"; - - -int __test_get_collection_data_deploy() { - var ( gas_before, stack ) = invoke_method( get_collection_data, [] ); - - int idx_next = stack.first(); - int forward_amount = one_ton / 10; - int query_id = rand( 1337 ) + 1; - cell coll_content = stack.second(); - slice owner = stack.third(); - - cell nft_content = begin_cell().store_slice("my_nft.json").end_cell(); - cell nft_init = begin_cell().store_slice(owner).store_ref(nft_content).end_cell(); - - builder msg_body = generate_nft_deploy_request( idx_next, nft_init, query_id, forward_amount); - cell msg = generate_internal_message_custom(0, 0, 0, msg_body, owner, null(), 0); - - var ( gas_deploy, _ ) = invoke_method(recv_internal, [one_ton, one_ton, msg, msg_body.end_cell().begin_parse()]); - var ( gas_after, stack ) = invoke_method( get_collection_data, [] ); - ;;Index should increase by one - throw_unless( 500, stack.first() == idx_next + 1 ); - - ;; Nothing should change here - throw_unless( 501, equal_slices( coll_content.begin_parse(), stack.second().begin_parse() ) ); - throw_unless( 502, equal_slices( owner, stack.third() ) ); - - return gas_before + gas_after + gas_deploy; -} - -int __test_get_collection_data_change_owner() { - - var ( gas_before, stack ) = invoke_method( get_collection_data, [] ); - - int query_id = rand( 1337 ) + 1; - int change_owner = 3; ;;WARNING contract dependent value. - slice owner = stack.third(); - slice new_owner = generate_internal_address_with_custom_data(0, 0, random()); - builder msg_body = generate_internal_message_body( change_owner, query_id ).store_slice( new_owner ); - cell msg = generate_internal_message_custom( 0, 0, 0, msg_body, owner, null(), 0 ); - - var ( gas_change, _ ) = invoke_method( recv_internal, [one_ton, one_ton, msg, msg_body.end_cell().begin_parse()] ); - var ( gas_after, stack ) = invoke_method( get_collection_data, [] ); - - throw_unless( 500, equal_slices( new_owner, stack.third() ) ); - - return gas_before + gas_change + gas_after; -} - -int __test_get_nft_address_by_index() { - - slice owner = get_owner(); - - ;; To test that nft address is correct - ;; we have to deploy one first and capture it's address - ;; Index:0 - - cell nft_content = begin_cell().store_slice("my_nft.json").end_cell(); - cell nft_init = begin_cell().store_slice(owner).store_ref(nft_content).end_cell(); - - int query_id = rand(1337) + 1; - int forward_amount = one_ton / 10; - int deploy_idx = 0; - - builder msg_body = generate_nft_deploy_request(deploy_idx, nft_init, query_id, forward_amount); - cell msg = generate_internal_message_custom(0, 0, 0, msg_body, owner, null(), 0); - - (int gas_used, _) = invoke_method(recv_internal, [one_ton, 0, msg, msg_body.end_cell().begin_parse()]); - ;; Should successfully deploy and produce singe deployment message - - tuple actions = parse_c5(); - throw_unless(500, actions.tuple_length() == 1); - - (int action_type, cell body, int mode) = actions.at(0).untriple(); - throw_unless(501, action_type == 0); - - tuple parsed_msg = unsafe_tuple(parse_internal_message(body)); - - ;;Picking nft address from deployment message - slice nft_address = parsed_msg.at(4); - - var (gas_used2, stack) = invoke_method(get_nft_address_by_index, [deploy_idx]); - - throw_unless(502, stack.tuple_length() == 1); - - throw_unless(503, equal_slices(nft_address, stack.first())); - - - return gas_used; -} - diff --git a/nft_tests/tests/collection-tests.func b/nft_tests/tests/collection-tests.func deleted file mode 100644 index c947272..0000000 --- a/nft_tests/tests/collection-tests.func +++ /dev/null @@ -1,273 +0,0 @@ -#pragma version >=0.2.0; - -#include "utils/collection-data.func"; -#include "utils/constants.func"; -#include "../func/utils/op-codes.func"; - - -;; Pretty unpleasent tuple to work with. -;; Flags are mixed up with data that is not always inserted - -cell get_state_init_field(tuple state_init, int idx) inline { - cell res = null(); - ;; Next flag index - int next_idx = 0; - - do { - int flag = state_init.at(next_idx); - - next_idx = flag ? next_idx + 2 : next_idx + 1; - idx -= 1; - - if(idx == 0 & flag) { - res = state_init.at(next_idx - 1); - } - - } until (~ res.null?() | idx <= 0) - - return res; -} - -_ validate_TIP_64(slice content_data) impure inline { - - int content_layout = content_data~load_uint(8); - - ;; Check for allowed content_layout - throw_unless(305, (content_layout == 1) | (content_layout == 0)); - - if(content_layout == 1) { - - ;; Check that off-chain URI contains at least one ASCII char - throw_unless(306, token_snake_len(content_data) > 8); - } else { - ;; On-chain is stored as dict - ;; Has to be non-empty - throw_if(306, content_data.preload_dict().dict_empty?()); - - ;; Perhaps could go further and test for Optional dict keys but none of those are required so i'll leave it be - ;; For now - } -} - -int __test_change_owner() { - - {- - This method is not defined in TIP-62. - Still this is de-facto standard. - One could argue that this one should - be moved elsewhere. - -} - - var (owner_address, prev_item_index, content, nft_item_code, royalty_params) = load_test_data(); ;; this data will load from c4 - - int change_owner = 3; ;;Change owner op could be different - int query_id = rand(1337) + 1; - slice rand_addr = generate_internal_address_with_custom_data(0, 0, random()); - ;;Setting this random address as new collection owner. - builder msg_body = generate_internal_message_body( change_owner, query_id ).store_slice( rand_addr ); - cell msg = generate_internal_message_custom( 0, 0, 0, msg_body, rand_addr, null(), 0 ); - - ;; Should fail from non-owner address - int gas_failed = invoke_method_expect_fail( recv_internal, [one_ton, one_ton, msg, msg_body.end_cell().begin_parse()] ); - - ;;With correct owner it should succeed - msg = generate_internal_message_custom( 0, 0, 0, msg_body, owner_address, null(), 0 ); - var ( gas_success, _ ) = invoke_method( recv_internal, [one_ton, 0, msg, msg_body.end_cell().begin_parse()] ); - - throw_unless( 500, equal_slices( rand_addr, get_owner() ) ); - - - return gas_success + gas_failed; -} - -int __test_deploy_item() { - {- - Collection deployment behaviour is not really defined in TIP-62 - Besides get methods. - Still have to test that it at least barely works right? - Will test: - - 1) Will test just that it won't allow deployment from non-owner - 2) Successfull deployment increases index and sends apropriate msgs - -} - - var (owner_address, prev_item_index, content, nft_item_code, royalty_params) = load_test_data(); ;; this data will load from c4 - - slice rand_addr = generate_internal_address_with_custom_data(0, 0, random()); - - {- - This is not the actual content format required for successfull deployment. - Contrant will accept it and i think it's something to fix - cell nft_content = begin_cell().store_uint( 1, 8 ).store_slice("my_nft.json").end_cell(); - -} - - cell nft_content = begin_cell().store_slice("my_nft.json").end_cell(); - ;; That's what actually should be sent for successfull deployment - cell nft_init = begin_cell().store_slice(owner_address).store_ref(nft_content).end_cell(); - int query_id = rand(1337) + 1; - int forward_amount = one_ton / 10; - builder msg_body = generate_nft_deploy_request(0, nft_init, query_id, forward_amount); - cell msg = generate_internal_message_custom(0, 0, 0, msg_body, rand_addr, null(), 0); - ;; Should not allow deploy from non-owner address - int gas_used = invoke_method_expect_fail(recv_internal, [one_ton, 0, msg, msg_body.end_cell().begin_parse()]); - assert_no_actions(); - ;; Verify that non-owner address was error trigger - ;; Expect success - - cell msg = generate_internal_message_custom(0, 0, 0, msg_body, owner_address, null(), 0); - (int gas_success, _) = invoke_method(recv_internal, [one_ton, 0, msg, msg_body.end_cell().begin_parse()]); - - tuple actions = parse_c5(); - - throw_unless(500, actions.tuple_length() == 1); - - (int action_type, cell body, int mode) = actions.at(0).untriple(); - - throw_unless(501, action_type == 0); - - tuple parsed_msg = unsafe_tuple(parse_internal_message(body)); - - throw_unless(502, forward_amount == parsed_msg.at(5)); - - tuple state_init = parsed_msg.at(7); - - ;; Deployment message has to have state_init - int state_len = state_init.tuple_length(); - - throw_unless(503, state_len > 0); - - ;; Fourth flag is data segment of state_init - - cell init_data = state_init.get_state_init_field(4); - - ;; Data segment has to be present in ntf StateInit - - throw_if(504, init_data.null?()); - - ;; Content in message body should be equal to what we sent - - throw_unless(505, equal_slices(nft_init.begin_parse(), parsed_msg.at(8))); - - ;; Nft index has to increase - - throw_unless(506, get_nft_index() > prev_item_index); - - return gas_used + gas_success; -} - -int __test_royalty_msg () { - - ( int numirator, int denominator, slice dst ) = get_test_royalty(); ;;Could be load_test_royalty - - int query_id = rand( 1337 ) + 1; - slice src_addr = generate_internal_address_with_custom_data( 0, 0, random() ); - builder msg_body = generate_get_royalty_params( query_id ); - cell msg = generate_internal_message_custom( 0, 0, 0, msg_body, src_addr, null(), 0 ); - ( int gas_used, _ ) = invoke_method( recv_internal, [ one_ton, one_ton, msg, msg_body.end_cell().begin_parse() ] ); - tuple actions = parse_c5(); - - throw_unless( 500, actions.tuple_length() == 1 ); - - ( int action_type, cell body, int mode ) = actions.first().untriple(); - throw_unless( 501, action_type == 0 ); - throw_unless( 502, mode == 64 ); - - - tuple parsed_msg = unsafe_tuple( parse_internal_message(body) ); - - ;;Message is sent back - throw_unless( 503, equal_slices( src_addr, parsed_msg.at( 4 ) ) ); - - slice msg_body = parsed_msg.at( 8 ); - throw_unless( 504, op_report_royalty_params == msg_body~load_uint( 32 ) ); - throw_unless( 505, query_id == msg_body~load_uint( 64 ) ); - throw_unless( 506, numirator == msg_body~load_uint( 16 ) ); - throw_unless( 507, denominator == msg_body~load_uint( 16 ) ); - throw_unless( 508, equal_slices( dst, msg_body~load_msg_addr() ) ); - - - return gas_used; -} - -;; Get methods test cases - -int __test_get_collection_data() { - - var (owner_address, next_item_index, content, nft_item_code, royalty_params) = load_test_data(); - - var (gas_used, stack) = invoke_method(get_collection_data, []); - ;; Should return 3 values - - throw_unless(600, stack.tuple_length() == 3); - - ;; Index, content and owner address should equal to c4 - - int res_idx = stack.first(); - throw_unless(601, next_item_index == res_idx); - - ;; First ref of a content is returned - ;; Collection content - - cell res_content = stack.second(); - slice cont_slice = content.begin_parse(); - cell coll_content = cont_slice~load_ref(); - - throw_unless(602, equal_slices(coll_content.begin_parse(), res_content.begin_parse())); - - slice res_owner = stack.third(); - throw_unless(603, equal_slices(owner_address, res_owner)); - - ;; Let's check that CollectionContent is compliant to TIP-64 - - validate_TIP_64(coll_content.begin_parse()); - - - return gas_used; -} - - -int __test_get_nft_content() { - - ;;Is there anything to be integrationally tested here? - ;;Probably not - - var (owner_address, next_item_index, content, nft_item_code, royalty_params) = load_test_data(); - slice coll_slice = content.begin_parse(); - cell nft_content = begin_cell().store_slice("my_nft.json").end_cell(); - - (_, cell collection_comm) = (coll_slice~load_ref(), coll_slice~load_ref()); - cell concat_content = snake_concat_tagged(1, collection_comm, nft_content); - - var (gas_used, stack) = invoke_method(get_nft_content, [0, nft_content]); - - cell res = stack.at(0); - slice res_slice = res.begin_parse(); - - throw_unless(601, snake_equal?(concat_content.begin_parse(), res_slice)); - - ;; Has to comply with TIP-64 - validate_TIP_64(res_slice); - - return gas_used; - -} - -{- - get_nft_address_by_index requires deployed nft item. - Thus it has been moved to integrational tests. --} - -int __test_royalty_params() { - - var ( gas_used, stack ) = invoke_method( royalty_params, [] ); - - throw_unless( 700, stack.tuple_length() == 3 ); - throw_unless( 701, is_int( stack.first() ) ); - throw_unless( 702, is_int( stack.second() ) ); - throw_unless( 703, is_slice( stack.third() ) ); - - parse_std_addr( stack.third() ); - - return gas_used; -} - diff --git a/nft_tests/tests/nft-tests-int.func b/nft_tests/tests/nft-tests-int.func deleted file mode 100644 index 28684e0..0000000 --- a/nft_tests/tests/nft-tests-int.func +++ /dev/null @@ -1,24 +0,0 @@ -#pragma version >=0.2.0; - -#include "utils/constants.func"; - -int __test_get_nft_data_transfer() { - ;; (int init?, int index, slice collection_address, slice owner_address, cell individual_content) - - var ( _ , new_owner, resp_dst ) = setup_transfer_addresses( false ); - var ( gas_before, stack ) = invoke_method( get_nft_data, [] ); - - slice owner = stack.fourth(); - int query_id = rand( 1337 ) + 1; - builder msg_body = generate_nft_transfer_request( new_owner, resp_dst, query_id, null(), 0, null(), 0); - cell msg = generate_internal_message_custom( 0, 0, 0, msg_body, owner, null(), 0 ); - - var ( gas_transfer, _ ) = invoke_method( recv_internal, [ one_ton, one_ton, msg, msg_body.end_cell().begin_parse() ] ); - var ( gas_after, stack ) = invoke_method( get_nft_data, [] ); - - throw_unless( 500, equal_slices( new_owner, stack.fourth() ) ); - - - return gas_before + gas_transfer + gas_after; -} - diff --git a/nft_tests/tests/nft-tests.func b/nft_tests/tests/nft-tests.func deleted file mode 100644 index 07ac943..0000000 --- a/nft_tests/tests/nft-tests.func +++ /dev/null @@ -1,584 +0,0 @@ -#pragma version >=0.2.0; - -#include "utils/constants.func"; -#include "../func/utils/op-codes.func"; - -slice get_owner_addr() inline { - slice ds = get_data().begin_parse().skip_bits( 64 ); - ds~load_msg_addr(); - return ds~load_msg_addr(); -} - -slice gen_new_owner( slice old_owner ) inline { - slice new_owner = generate_internal_address_with_custom_data( 0, 0, random() ); - - ;; In theory we can win a bingo and get same 256b integer from RNG - while( equal_slices( new_owner, old_owner ) ){ - new_owner = generate_internal_address(); - } - - return new_owner; -} - -( int, slice, slice, cell ) load_test_data() inline { - slice ds = get_data().begin_parse(); - int idx = ds~load_uint( 64 ); - slice coll_addr = ds~load_msg_addr(); - slice owner = ds~load_msg_addr(); - - ;;We need all addresses on Workchain 0 otherwise force_chain will throw 333 - - if (coll_addr.preload_uint(2) != 0){ ;; if not addr_none - coll_addr.force_chain(); - } - - if (owner.preload_uint(2) != 0){ - owner.force_chain(); - } - - return ( idx, coll_addr, owner, ds~load_ref() ); -} - -( slice, slice, slice ) setup_transfer_addresses( int has_dst? ) inline { - slice owner_addr = get_owner_addr(); - - ;;We need all addresses on Workchain 0 otherwise force_chain will throw 333 - - slice new_owner = gen_new_owner( owner_addr ); - - slice resp_dst = has_dst? ? generate_internal_address_with_custom_data( 0, 0, random() ) : generate_empty_address(); - - return ( owner_addr, new_owner, resp_dst ); - -} - -_ verify_ownership_assigned( int query_id, slice owner, slice new_owner, int forward_amount, slice forward_payload, cell msg ) impure inline { - {- - TL-B schema: ownership_assigned#05138d91 query_id:uint64 prev_owner:MsgAddress - forward_payload:(Either Cell ^Cell) = InternalMsgBody; - query_id should be equal with request's query_id. - We verify that: - 1) message is sent to the new_owner address - 2) attached amount matches forward_amount - 3) op matches ownership_assigned op - 4) query_id matches query_id sent - 5) forward_payload matches payload set - -} - - tuple parsed_msg = unsafe_tuple( parse_internal_message( msg ) ); - - throw_unless(601, equal_slices( new_owner, parsed_msg.at( 4 ) ) ); - ;; Check that forward_amount matches - throw_unless(602, forward_amount == parsed_msg.at( 5 ) ); - - slice msg_body = parsed_msg.at( 8 ); - - throw_unless( 603, op_ownership_assigned == msg_body~load_uint( 32 ) ); - - throw_unless( 604, query_id == msg_body~load_uint( 64 ) ); - - ;;Checking for previous owner addr - throw_unless( 605, equal_slices( owner, msg_body~load_msg_addr() ) ); - - if( ~ null?( forward_payload ) ){ - if( msg_body~load_uint( 1 ) ){ - cell msg_payload = msg_body~load_ref(); - throw_unless( 606, equal_slices( forward_payload, msg_payload.begin_parse() ) ); - } - else { - throw_unless( 606, equal_slices( forward_payload, msg_body ) ); - } - } -} - -_ verify_excess_sent( int query_id, slice resp_dst, int balance, int forward_fee, int forward_amount, cell msg ) impure inline { - {- - TL-B schema: excesses#d53276db query_id:uint64 = InternalMsgBody; - Excess message should be sent to resp_dst with all of the msg_value - fees taken to process - We verify that: - 1) message is sent to resp_dst - 2) attached amount is balance - fees taken - forward_amount - 3) op matches excess op - 4) query_id matches request query_id - -} - - tuple parsed_msg = unsafe_tuple( parse_internal_message( msg ) ); - - ;;Check dst_addr to be equal to resp_dst - throw_unless( 701, equal_slices( resp_dst, parsed_msg.at( 4 ) ) ); - - int total_sent = parsed_msg.at( 5 ); - int should_sent = balance - min_storage - forward_fee; - - if( forward_amount > 0 ) { - should_sent -= forward_amount + forward_fee; - } - - throw_unless( 702, should_sent == total_sent ); - - slice msg_body = parsed_msg.at( 8 ); - - throw_unless( 703, op_excesses == msg_body~load_uint( 32 ) ); - - throw_unless( 704, query_id == msg_body~load_uint( 64 ) ); - -} - -;;Transfer ownership reject cases - -int __test_transfer_not_owner(){ - - var ( owner_addr, new_owner, resp_dst ) = setup_transfer_addresses( true ); - - ;;Trying to execute transfer ownership from non-owner addr - - builder msg_body = generate_nft_transfer_request( new_owner, resp_dst, 12345, null(), one_ton / 10, null(), 0 ); - cell msg = generate_internal_message_custom( 0, 0, 0, msg_body, new_owner, null(), 0 ); - - ;;Should fail - int gas_used = invoke_method_expect_fail( recv_internal, [ one_ton, 0, msg, msg_body.end_cell().begin_parse() ] ); - - {- - These are for testing this specific contract exit_code values invoke_method_expect_fail is more general way - var ( exit_code, gas_used, _ ) = invoke_method_full( recv_internal, [ one_ton, 0, msg, msg_body.end_cell().begin_parse() ] ); - throw_unless( exit_code, exit_code == 401 ); - Should not generate any actions - -} - - assert_no_actions(); - - ;;Now let's verify that owner_addr triggered fail by changin source addr to actual owner_addr - ;;Expect contract to return success - - msg = generate_internal_message_custom( 0, 0, 0, msg_body, owner_addr, null(), 0 ); - - ( int gas_success , _ ) = invoke_method( recv_internal, [ one_ton, 0, msg, msg_body.end_cell().begin_parse() ] ); - - return gas_used + gas_success; - -} - -int __test_transfer_forward_amount_too_large() { - - var ( owner_addr, new_owner, resp_dst ) = setup_transfer_addresses( true ); - - {- - NFT should reject transfer if balance lower than forward_amount + message forward fee + minimal storage fee - Sending message with forward_amount of 1 TON and balance 0.1 TON - Now using legit owner address - TON balance:0.1 forward_amount:1 TON fwd_fee:0 verifying that forward_amount is taken into account - Should fail with no actions - -} - - builder msg_body = generate_nft_transfer_request( new_owner, resp_dst, 12345, null(), one_ton, null(), 0 ); - cell msg = generate_internal_message_custom( 0, 0, 0, msg_body, owner_addr, null(), 0 ); - int gas_used = invoke_method_expect_fail( recv_internal, [ one_ton / 10, 0, msg, msg_body.end_cell().begin_parse() ] ); - - ;;(int exit_code, _, _ ) = invoke_method_full( recv_internal, [ one_ton, 0, msg, msg_body.end_cell().begin_parse() ] ); - ;;throw_unless( exit_code, exit_code == 402 ); - - assert_no_actions(); - - ;;Now verify that balance was the error trigger by increasing contract balance to 10 TONs - ;;Expect success - - ( int gas_success, _ ) = invoke_method( recv_internal, [ one_ton * 10, 0, msg, msg_body.end_cell().begin_parse() ] ); - - - return gas_used + gas_success; -} - -int __test_transfer_storage_fee() { - - var ( owner_addr, new_owner, resp_dst ) = setup_transfer_addresses( true ); - - {- - Now let's try forward_amount exactly equal to balance and fwd_fee 0 - 1 TON Balance forward_amount:1 TON fwd_fee:0 verifying that minimal storage comes into play - Should fail with no actions - -} - - builder msg_body = generate_nft_transfer_request( new_owner, resp_dst, 12345, null(), one_ton, null(), 0 ); - cell msg = generate_internal_message_custom( 0, 0, 0, msg_body, owner_addr, null(), 0 ); - - int gas_used = invoke_method_expect_fail( recv_internal, [ one_ton, 0, msg, msg_body.end_cell().begin_parse() ] ); - - assert_no_actions(); - - ;; Let's verify that storage fee was an error trigger by increasing balance by min_storage - ;; Expect success - - ( int gas_success, _ ) = invoke_method( recv_internal, [ one_ton + min_storage, 0, msg, msg_body.end_cell().begin_parse() ] ); - - - return gas_used + gas_success; -} - -int __test_transfer_forward_fee_single() { - {- - If transfer is successfull NFT supposed to send up to 2 messages - 1)To the owner_address with forward_amount of coins - 2)To the response_addr with forward_payload if response_addr is not addr_none - Each of those messages costs fwd_fee - Let' test the first case only by setting resp_dst to addr_none - -} - - var ( owner_addr, new_owner, resp_dst ) = setup_transfer_addresses( false ); - - {- - Now we test if contract takes forward fee into account by adding forward fee to the incoming message - Contract balance would be 1TON + storage_fee and fwd_fee would be 0.01 TON - Should fail with no actions - -} - - int forward_fee = one_ton / 100; - int balance = one_ton + min_storage; - - builder msg_body = generate_nft_transfer_request( new_owner, resp_dst, 12345, null(), one_ton, null(), 0 ); - - cell msg = generate_internal_message_custom( 0, 0, 0, msg_body, owner_addr, null(), forward_fee); - - int gas_used = invoke_method_expect_fail( recv_internal, [ balance, 0, msg, msg_body.end_cell().begin_parse() ] ); - assert_no_actions(); - - ;; Let's verify that forward fee was an error trigger by increasing balance by that fee - ;; Expect success - - ( int gas_success, _ ) = invoke_method( recv_internal, [ balance + muldiv(forward_fee, 3, 2), 0, msg, msg_body.end_cell().begin_parse() ] ); - - - return gas_used; -} - -int __test_transfer_forward_fee_double() { - - {- - If transfer is successfull NFT supposed to send up to 2 messages - 1)To the owner_address with forward_amount of coins - 2)To the response_addr with forward_payload if response_addr is not addr_none - Each of those messages costs fwd_fee - In this case we test scenario where both messages required to be sent but balance has funs only for single message - To do so resp_dst has be a valid address not equal to addr_none - -} - - var ( owner_addr, new_owner, resp_dst ) = setup_transfer_addresses( true ); - - {- - Now we test if contract takes forward fee into account by adding forward fee to the incoming message - Contract balance would be 1TON + storage_fee and fwd_fee would be 0.01 TON - Should fail with no actions - -} - - int forward_fee = one_ton / 100; - int balance = one_ton + min_storage + muldiv(forward_fee, 3, 2); - - builder msg_body = generate_nft_transfer_request( new_owner, resp_dst, 12345, null(), one_ton, null(), 0 ); - - cell msg = generate_internal_message_custom( 0, 0, 0, msg_body, owner_addr, null(), forward_fee); - - int gas_used = invoke_method_expect_fail( recv_internal, [ balance, 0, msg, msg_body.end_cell().begin_parse() ] ); - assert_no_actions(); - - ;; Let's verify that double forward fee was an error trigger by increasing balance by another forward_fee - ;; Expect success - - ( int gas_success, _ ) = invoke_method( recv_internal, [ balance + muldiv(forward_fee, 3, 2), 0, msg, msg_body.end_cell().begin_parse() ] ); - - - return gas_used; -} - -int __test_success_no_forward_no_reponse() { - - {- - forward_amount:0 resp_dst:addr_none - On successfull execution only address change should occur. - Expect no messages to be sent. - -} - - var ( owner_addr, new_owner, resp_dst ) = setup_transfer_addresses( false ); - - builder msg_body = generate_nft_transfer_request( new_owner, resp_dst, 12345, null(), 0, null(), 0 ); - cell msg = generate_internal_message_custom( 0, 0, 0, msg_body, owner_addr, null(), 0 ); - - (int gas_used, _ ) = invoke_method( recv_internal, [ one_ton, 0, msg, msg_body.end_cell().begin_parse() ] ); - - ;; Owner address has to be changed to the new_owner - throw_unless( 100, equal_slices( get_owner_addr(), new_owner ) ); - - ;; resp_dst is addr_none and no forward_amount, so no message should be generated - assert_no_actions(); - - return gas_used; - -} - -;; Transfer ownership success cases - -int __test_transfer_success_forward_no_response() { - - {- - forward_amount:1TON resp_dst: addr_none balance:10TON - forward_payload:"Hop hey!" - On successfull execution expect: - 1) Address change to new owner - 2) Single ownership_assigned message is sent to new_owner addr with forward_amount attached - TL-B schema: ownership_assigned#05138d91 query_id:uint64 prev_owner:MsgAddress - forward_payload:(Either Cell ^Cell) = InternalMsgBody; - query_id should be equal with request's query_id. - - forward_payload should be equal with request's forward_payload. - - prev_owner is address of the previous owner of this NFT item. - -} - - var ( owner_addr, new_owner, resp_dst ) = setup_transfer_addresses( false ); - - int forward_fee = one_ton / 100; - int forward_amount = one_ton; - int balance = one_ton * 10; - int query_id = rand( 1337 ) + 1; - builder forward_payload = begin_cell().store_slice("Hop hey!"); - - - builder msg_body = generate_nft_transfer_request( new_owner, resp_dst, query_id, null(), forward_amount, forward_payload.end_cell(), 0); - cell msg = generate_internal_message_custom( 0, 0, 0, msg_body, owner_addr, null(), forward_fee ); - - (int gas_used, _ ) = invoke_method( recv_internal, [ balance, 0, msg, msg_body.end_cell().begin_parse() ] ); - - ;; Owner address has to be changed to the new_owner - throw_unless( 100, equal_slices( get_owner_addr(), new_owner ) ); - - tuple actions = parse_c5(); ;; test-libs/c5_parse_helpers.func - - int actions_count = actions.tuple_length(); - - ;; Only one message should be sent - throw_unless( 500 + actions_count, actions_count == 1 ); - - ( int action_type, cell body, int mode ) = actions.at(0).untriple(); - - ;; Action has to be action_send_msg - throw_unless( 600, action_type == 0); - - verify_ownership_assigned( query_id, owner_addr, new_owner, forward_amount, forward_payload.end_cell().begin_parse(), body ); - - - return gas_used; -} - -int __test_transfer_success_forward_with_response() { - - {- - forward_amount:1TON resp_dst: addr_none balance:10TON - forward_payload:"Hop hey!" - On successfull execution expect: - 1) Address change to new owner - 2) Single ownership_assigned message is sent to new_owner addr with forward_amount attached - TL-B schema: ownership_assigned#05138d91 query_id:uint64 prev_owner:MsgAddress - forward_payload:(Either Cell ^Cell) = InternalMsgBody; - query_id should be equal with request's query_id. - - forward_payload should be equal with request's forward_payload. - - prev_owner is address of the previous owner of this NFT item. - - 3) Single excesses message to resp_dst - TL-B schema: excesses#d53276db query_id:uint64 = InternalMsgBody; - - For second message to be sent resp_dst should be valid address not equal to addr_none - -} - - var ( owner_addr, new_owner, resp_dst ) = setup_transfer_addresses( true ); - - int forward_fee = one_ton / 100; - int forward_amount = one_ton; - int balance = one_ton * 10; - int query_id = rand( 1338 ) + 1; - builder forward_payload = begin_cell().store_slice("Hop hey!"); - - - builder msg_body = generate_nft_transfer_request( new_owner, resp_dst, query_id, null(), forward_amount, forward_payload.end_cell(), 0); - cell msg = generate_internal_message_custom( 0, 0, 0, msg_body, owner_addr, null(), forward_fee ); - - ;;Attach 5 TONs to message value to test excess - (int gas_used, _ ) = invoke_method( recv_internal, [ balance, 0, msg, msg_body.end_cell().begin_parse() ] ); - - ;; Owner address has to be changed to the new_owner - throw_unless( 100, equal_slices( get_owner_addr(), new_owner ) ); - - tuple actions = parse_c5(); ;; test-libs/c5_parse_helpers.func - - int actions_count = actions.tuple_length(); - - ;; Two messages should be sent - throw_unless( 500 + actions_count, actions_count == 2 ); - - ( int action_type, cell body, int mode ) = actions.at(0).untriple(); - - ;; Action has to be action_send_msg - throw_unless( 600, action_type == 0); - - verify_ownership_assigned( query_id, owner_addr, new_owner, forward_amount, forward_payload.end_cell().begin_parse(), body ); - - ( action_type, body, mode ) = actions.at( 1 ).untriple(); - - throw_unless( 700, action_type == 0); - - verify_excess_sent( query_id, resp_dst, balance, muldiv(forward_fee, 3, 2), forward_amount, body ); - - return gas_used; -} - - -int __test_transfer_success_response_only() { - - {- - forward_amount:0 TON resp_dst: valid address balance:10TON - forward_payload:"Hop hey!" - On successfull execution expect: - 1) Address change to new owner - 2) Single excesses message to resp_dst - TL-B schema: excesses#d53276db query_id:uint64 = InternalMsgBody; - - For second message to be sent resp_dst should be valid address not equal to addr_none - -} - - var ( owner_addr, new_owner, resp_dst ) = setup_transfer_addresses( true ); - - int forward_fee = one_ton / 100; - int forward_amount = 0; - int balance = one_ton * 10; - int query_id = rand( 1337 ) + 1; - builder forward_payload = begin_cell().store_slice("Hop hey!"); - - - builder msg_body = generate_nft_transfer_request( new_owner, resp_dst, query_id, null(), forward_amount, forward_payload.end_cell(), 0); - cell msg = generate_internal_message_custom( 0, 0, 0, msg_body, owner_addr, null(), forward_fee ); - - (int gas_used, _ ) = invoke_method( recv_internal, [ balance, 0, msg, msg_body.end_cell().begin_parse() ] ); - - ;; Owner address has to be changed to the new_owner - throw_unless( 100, equal_slices( get_owner_addr(), new_owner ) ); - - tuple actions = parse_c5(); ;; test-libs/c5_parse_helpers.func - - int actions_count = actions.tuple_length(); - - ;; Only one message should be sent - throw_unless( 500 + actions_count, actions_count == 1 ); - - ( int action_type, cell body, int mode ) = actions.at(0).untriple(); - - ;; Action has to be action_send_msg - throw_unless( 600, action_type == 0); - - verify_excess_sent( query_id, resp_dst, balance, muldiv(forward_fee, 3, 2), forward_amount, body ); - - return gas_used; -} - -;;get_static_data test cases - -int __test_get_static_data() { - {- - Sends get_static_data request. - Expect success. - On success execution expect: - 1) Single report_static_data message received - 2) Message send mode is 64 - 3) query_id matches query_id in request - 4) Message has 256 bit index value - 5) Message has valid collection_address matching c4 collection_addr - -} - - int query_id = rand( 1337 ) + 1; - - var ( idx, coll_addr, owner, _ ) = load_test_data(); - builder msg_body = generate_nft_get_static_data_request( query_id ); - cell msg = generate_internal_message_custom( 0, 0, 0, msg_body, owner, null(), 0 ); - - ( int gas_used, _ ) = invoke_method( recv_internal,[ one_ton * 10, 0, msg, msg_body.end_cell().begin_parse() ] ); - - tuple actions = parse_c5(); - - throw_unless( 800, actions.tuple_length() == 1 ); - - ( int action_type, cell body, int mode ) = actions.at(0).untriple(); - - throw_unless( 801, action_type == 0 ); - - throw_unless( 802, mode == 64 ); - - tuple parsed_msg = unsafe_tuple( parse_internal_message( body ) ); - slice ds = parsed_msg.at( 8 ); - - ( int op, int resp_query_id ) = ( ds~load_uint( 32 ), ds~load_uint( 64 ) ); - - throw_unless( 803, op_report_static_data == op ); - - throw_unless( 804, query_id == resp_query_id ); - - throw_unless( 805, idx == ds~load_uint( 256 ) ); - - throw_unless( 806, equal_slices( coll_addr, ds~load_msg_addr() ) ); - - return gas_used; -} - -;; Get methods test - -int __test_get_nft_data() { - ;; (int init?, int index, slice collection_address, slice owner_address, cell individual_content) - - var ( idx, coll_addr, owner, content ) = load_test_data(); - - ( int gas_used, tuple stack ) = invoke_method( get_nft_data, [] ); - - ;;Check that argument count match signature - - throw_unless( 300, stack.tuple_length() == 5 ); - int init? = stack.first(); - int res_idx = stack.second(); - slice res_collection = stack.third(); - slice res_owner = stack.fourth(); - cell res_content = stack.at( 4 ); - - ;; Check that initialized NFT can't have empty owner_address - ;; And the other way around - throw_unless( 301, ( init? ^ res_owner.null?() ) ); - - ;; Check that contract correctly loads data - throw_unless( 302, equal_slices( owner, res_owner ) ); - throw_unless( 303, equal_slices( coll_addr, res_collection ) ); - - slice content_data = res_content.begin_parse(); - - throw_unless( 304, equal_slices( content.begin_parse(), content_data ) ); - - ;; Only collection-less NFT have to comply with TIP-64 - ;; Such NFT items have addr_none as collection_address - if( equal_slices( res_owner, generate_empty_address() ) ){ - - int content_layout = content_data~load_uint( 8 ); - - ;; Check for allowed content_layout - throw_unless( 305, ( content_layout == 1 ) | ( content_layout == 0 ) ); - - - if( content_layout == 1 ){ - - ;; Check that off-chain URI contains at least one ASCII char - throw_unless( 306, token_snake_len( content_data ) > 8 ); - } else { - ;; On-chain is stored as dict - ;; Has to be non-empty - throw_if( 306, content_data.preload_dict().dict_empty?() ); - - ;; Perhaps could go further and test for Optional dict keys but none of those is required so i'll leave it be - } - } - - - return gas_used; -} diff --git a/nft_tests/tests/utils/collection-data.func b/nft_tests/tests/utils/collection-data.func deleted file mode 100644 index e1bdf33..0000000 --- a/nft_tests/tests/utils/collection-data.func +++ /dev/null @@ -1,38 +0,0 @@ -(slice, int, cell, cell, cell) load_test_data() inline { - var ds = get_data().begin_parse(); - return ( ds~load_msg_addr(), ;; owner_address - ds~load_uint(64), ;; next_item_index - ds~load_ref(), ;; content - ds~load_ref(), ;; nft_item_code - ds~load_ref() ;; royalty_params - ); -} - -slice get_owner() { - (slice owner, _, _, _, _) = load_test_data(); - return owner; -} - -cell get_content() inline { - (_, _, cell content, _, _) = load_test_data(); - return content; -} - -int get_nft_index() inline { - (_, int idx, _, _, _) = load_test_data(); - - return idx; -} - -( int, int, slice ) load_test_royalty() { - ( _, _, _, _, cell royalty ) = load_test_data(); - slice ds = royalty.begin_parse(); - - return ( ds~load_uint( 16 ), ds~load_uint( 16 ), ds~load_msg_addr() ); -} - -( int, int, slice ) get_test_royalty() { - ( _, tuple stack ) = invoke_method( royalty_params, [] ); - - return ( stack.first(), stack.second(), stack.third() ); -} diff --git a/nft_tests/tests/utils/constants.func b/nft_tests/tests/utils/constants.func deleted file mode 100644 index 42f8c31..0000000 --- a/nft_tests/tests/utils/constants.func +++ /dev/null @@ -1,2 +0,0 @@ -const int one_ton = 1000000000; ;;10^9 -const int min_storage = 50000000; ;; 0.05 TON minimal storage fee