generated from ton-community/ton-onboarding-challenge
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 5181b7c
Showing
22 changed files
with
4,308 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
node_modules | ||
**/.DS_Store |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
# TON Onboarding Challenge Boilerplate | ||
|
||
Here you can become a true TVM Developer on the most advanced asynchronous blockchain in the world, The Open Network (TON). | ||
|
||
You can prove your talent using the most ancient and essential way in blockchains, starting from grandpa Bitcoin! As the first miners in TON, you will go through the Proof-of-Work smart contract and finally mine a secret reward for your TON wallet. | ||
|
||
## Who already completed the challenge? | ||
|
||
NFT Collection on GetGems with True TVM Developers: | ||
|
||
- [Mainnet NFT collection](https://getgems.io/collection/EQDk8N7xM5D669LC2YACrseBJtDyFqwtSPCNhRWXU7kjEptX) | ||
- [Testnet NFT collection](https://testnet.getgems.io/collection/EQDk8N7xM5D669LC2YACrseBJtDyFqwtSPCNhRWXU7kjEptX) | ||
|
||
## Step-by-step (simple) | ||
|
||
### Video Tutorial | ||
|
||
[](https://youtu.be/wEEQLwQy30Q) | ||
|
||
### Text tutorial | ||
|
||
* [TON Onboarding Challenge](https://ton.org/docs/develop/onboarding-challenge) | ||
|
||
|
||
|
||
## Mining process (deep dive) | ||
|
||
> Feel free to use a text version of tutorial to get help | ||
This is a NFT collection that differs from standard NFT collections in the following aspects: | ||
|
||
- normal NFT deployment is disabled and one can mint NFTs only by mining (similar to how PoW givers worked) | ||
- extra get-method is added for mining info | ||
|
||
In order to mine NFT you should send special **internal** message to collection contract with proof of work. | ||
|
||
You can get current `pow_complexity` by calling special get-function to get mining status called `get_mining_data` | ||
|
||
It returns a [tensor](https://ton.org/docs/develop/func/types#tensor-types) with the following data: | ||
|
||
``` | ||
( | ||
int pow_complexity, | ||
int last_success, | ||
int seed, | ||
int target_delta, | ||
int min_cpl, | ||
int max_cpl | ||
) | ||
``` | ||
|
||
Note that `seed` is set to a random value after every successful NFT mint. | ||
`pow_complexity` is also a dynamic value and could be changed from time to time. | ||
|
||
Layout of proof of work Cell: | ||
|
||
| Field | Type | Description | | ||
| :--- | :----: | ---: | | ||
| op | uint32 | Always 0x4d696e65 | | ||
| expire | uint32 | Unixtime of message expiration (should be some unixtime in future, but not greater than ~17 minutes in future.) | | ||
| whom | std_addr | Address of the miner (NFT owner would be set to this address) | | ||
| rdata1 | uint256 | Some random data | | ||
| seed | uint128 | Current seed | | ||
| rdata2 | uint256 | Should equal to rdata1 | | ||
|
||
Proof of work is considered to be valid only if: | ||
|
||
- rdata1 equals to rdata2 | ||
- (expire - now()) < 1024 | ||
- seed is equal to current seed stored in contract | ||
- hash of the proof of work message Cell is less than current pow_complexity (hash is compared as a big endian number) | ||
|
||
Basically you need to find such value for `rdata1` so that hash of the Cell is less than current `pow_complexity` | ||
|
||
After this an `internal` message with found Cell should be sent to collection with ~0.05 TON in order to mint NFT. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
|
||
// write your NFT miner here |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
module.exports = { | ||
preset: 'ts-jest', | ||
testEnvironment: 'node', | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
{ | ||
"name": "ton-onboarding-challenge", | ||
"version": "1.0.0", | ||
"main": "index.ts", | ||
"scripts": { | ||
"start": "ts-node index.ts" | ||
}, | ||
"license": "MIT", | ||
"dependencies": { | ||
"@ton-community/func-js": "^0.1.5", | ||
"bn.js": "^5.2.1", | ||
"qrcode-terminal": "^0.12.0", | ||
"ton": "^12.1.3", | ||
"ton-contract-executor": "^0.5.2", | ||
"typescript": "^4.8.4" | ||
}, | ||
"devDependencies": { | ||
"@types/bn.js": "^5.1.1", | ||
"@types/jest": "^29.1.2", | ||
"@types/node": "^18.8.4", | ||
"jest": "^29.1.2", | ||
"ts-jest": "^29.0.3", | ||
"ts-node": "^10.9.1" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
#include "stdlib.fc"; | ||
#include "storage.fc"; | ||
#include "op-codes.fc"; | ||
|
||
int ufits(int x, int bits) impure asm "UFITSX"; | ||
|
||
cell calculate_nft_item_state_init(int item_index, cell nft_item_code) { | ||
cell data = begin_cell().store_uint(item_index, 64).store_slice(my_address()).end_cell(); | ||
return begin_cell().store_uint(0, 2).store_dict(nft_item_code).store_dict(data).store_uint(0, 1).end_cell(); | ||
} | ||
|
||
slice calculate_nft_item_address(int wc, cell state_init) { | ||
return begin_cell().store_uint(4, 3) | ||
.store_int(wc, 8) | ||
.store_uint(cell_hash(state_init), 256) | ||
.end_cell() | ||
.begin_parse(); | ||
} | ||
|
||
() deploy_nft_item(int item_index, cell nft_item_code, cell nft_content) impure { | ||
cell state_init = calculate_nft_item_state_init(item_index, nft_item_code); | ||
slice nft_address = calculate_nft_item_address(0, state_init); | ||
var msg = begin_cell() | ||
.store_uint(0x18, 6) | ||
.store_slice(nft_address) | ||
.store_coins(0) | ||
.store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) | ||
.store_ref(state_init) | ||
.store_ref(nft_content); | ||
send_raw_message(msg.end_cell(), 64); ;; carry all the remaining value of the inbound message | ||
} | ||
|
||
() send_royalty_params(slice to_address, int query_id, slice data) impure inline { | ||
var msg = begin_cell() | ||
.store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool packages:MsgAddress -> 011000 | ||
.store_slice(to_address) | ||
.store_coins(0) | ||
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) | ||
.store_uint(op::report_royalty_params(), 32) | ||
.store_uint(query_id, 64) | ||
.store_slice(data); | ||
send_raw_message(msg.end_cell(), 64); ;; carry all the remaining value of the inbound message | ||
} | ||
|
||
() mint_nft(slice cs) impure { | ||
var hash = slice_hash(cs); | ||
throw_unless(24, hash < ctx_pow_complexity); ;; hash problem NOT solved | ||
|
||
var (op, expire, whom, rdata1, rseed, rdata2) = ( | ||
cs~load_uint(32), | ||
cs~load_uint(32), | ||
cs~load_msg_addr(), | ||
cs~load_uint(256), | ||
cs~load_uint(128), | ||
cs~load_uint(256) | ||
); | ||
ufits(expire - now(), 10); | ||
throw_unless(25, (rseed == ctx_seed) & (rdata1 == rdata2)); | ||
;; Proof of Work correct | ||
|
||
randomize_lt(); | ||
randomize(rdata1); | ||
|
||
;; recompute complexity | ||
int delta = now() - ctx_last_success; | ||
if (delta > 0) { | ||
int factor = muldivr(delta, 1 << 128, ctx_target_delta); | ||
factor = min(max(factor, 7 << 125), 9 << 125); ;; factor must be in range 7/8 .. 9/8 | ||
ctx_pow_complexity = muldivr(ctx_pow_complexity, factor, 1 << 128); ;; rescale complexity | ||
ctx_pow_complexity = min(max(ctx_pow_complexity, 1 << ctx_min_cpl), 1 << ctx_max_cpl); | ||
} | ||
|
||
ctx_last_success = now(); | ||
ctx_seed = random() >> 128; | ||
|
||
deploy_nft_item(ctx_next_item_index, ctx_nft_item_code, begin_cell() | ||
.store_slice(whom) | ||
.store_ref(begin_cell().end_cell()) | ||
.end_cell()); | ||
|
||
ctx_next_item_index += 1; | ||
|
||
store_base_data(); | ||
} | ||
|
||
() rescale_complexity(int expire) impure inline_ref { | ||
load_base_data(); | ||
int time = now(); | ||
throw_unless(28, time > expire); | ||
throw_unless(29, expire > ctx_last_success); | ||
int delta = time - ctx_last_success; | ||
throw_unless(30, delta >= ctx_target_delta * 16); | ||
accept_message(); | ||
int factor = muldivr(delta, 1 << 128, ctx_target_delta); | ||
int max_complexity = (1 << ctx_max_cpl); | ||
int max_factor = muldiv(max_complexity, 1 << 128, ctx_pow_complexity); | ||
ctx_pow_complexity = (max_factor < factor ? max_complexity : muldivr(ctx_pow_complexity, factor, 1 << 128)); | ||
ctx_last_success = time - ctx_target_delta; | ||
store_base_data(); | ||
} | ||
|
||
() recv_internal(cell in_msg_full, slice in_msg_body) impure { | ||
if (in_msg_body.slice_empty?()) { ;; ignore empty messages | ||
return (); | ||
} | ||
slice cs = in_msg_full.begin_parse(); | ||
int flags = cs~load_uint(4); | ||
|
||
if (flags & 1) { ;; ignore all bounced messages | ||
return (); | ||
} | ||
slice sender_address = cs~load_msg_addr(); | ||
load_base_data(); | ||
|
||
if (in_msg_body.preload_uint(32) == op::mine()) { | ||
mint_nft(in_msg_body); | ||
return (); | ||
} | ||
|
||
int op = in_msg_body~load_uint(32); | ||
int query_id = in_msg_body~load_uint(64); | ||
|
||
if (op == op::get_royalty_params()) { | ||
send_royalty_params(sender_address, query_id, ctx_royalty_params.begin_parse()); | ||
return (); | ||
} | ||
|
||
if (op == op::rescale_complexity()) { | ||
rescale_complexity(in_msg_body~load_uint(32)); | ||
return (); | ||
} | ||
|
||
throw_unless(401, equal_slices(sender_address, ctx_owner)); | ||
|
||
if (op == 3) { ;; change owner | ||
ctx_owner = in_msg_body~load_msg_addr(); | ||
store_base_data(); | ||
return (); | ||
} | ||
|
||
if (op == 4) { ;; change content | ||
ctx_content = in_msg_body~load_ref(); | ||
ctx_royalty_params = in_msg_body~load_ref(); | ||
store_base_data(); | ||
return (); | ||
} | ||
|
||
throw(0xffff); | ||
} | ||
|
||
;; Get methods | ||
|
||
var get_collection_data() method_id { | ||
load_base_data(); | ||
slice cs = ctx_content.begin_parse(); | ||
return (ctx_next_item_index, cs~load_ref(), ctx_owner); | ||
} | ||
|
||
var get_nft_address_by_index(int index) method_id { | ||
load_base_data(); | ||
cell state_init = calculate_nft_item_state_init(index, ctx_nft_item_code); | ||
return calculate_nft_item_address(0, state_init); | ||
} | ||
|
||
var royalty_params() method_id { | ||
load_base_data(); | ||
slice rs = ctx_royalty_params.begin_parse(); | ||
return (rs~load_uint(16), rs~load_uint(16), rs~load_msg_addr()); | ||
} | ||
|
||
var get_mining_data() method_id { | ||
load_base_data(); | ||
return ( | ||
ctx_pow_complexity, | ||
ctx_last_success, | ||
ctx_seed, | ||
ctx_target_delta, | ||
ctx_min_cpl, | ||
ctx_max_cpl | ||
); | ||
} | ||
|
||
var get_nft_content(int index, cell individual_nft_content) method_id { | ||
load_base_data(); | ||
slice cs = ctx_content.begin_parse(); | ||
cs~load_ref(); | ||
slice common_content = cs~load_ref().begin_parse(); | ||
return begin_cell() | ||
.store_uint(1, 8) ;; offchain tag | ||
.store_slice(common_content) | ||
.store_ref(individual_nft_content) | ||
.end_cell(); | ||
} |
Oops, something went wrong.