Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Nassharan authored Jun 28, 2024
0 parents commit 5181b7c
Show file tree
Hide file tree
Showing 22 changed files with 4,308 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
**/.DS_Store
75 changes: 75 additions & 0 deletions README.md
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

[![Watch the video](https://img.youtube.com/vi/wEEQLwQy30Q/0.jpg)](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.
2 changes: 2 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

// write your NFT miner here
4 changes: 4 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
25 changes: 25 additions & 0 deletions package.json
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"
}
}
193 changes: 193 additions & 0 deletions src/func/collection.fc
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();
}
Loading

0 comments on commit 5181b7c

Please sign in to comment.