8000 feat: add sharded jetton by skywardboundd · Pull Request #154 · tact-lang/jetton · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

feat: add sharded jetton #154

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
37 changes: 21 additions & 16 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
{
"private": true,
"scripts": {
"build": "yarn build:base && yarn build:governance && yarn build:feature-rich",
"build": "yarn build:base && yarn build:governance && yarn build:feature-rich && yarn build:shard",
"build:base": "tact --config ./tact.config.json --project Jetton",
"build:governance": "tact --config ./tact.config.json --project Governance",
"build:feature-rich": "tact --config ./tact.config.json --project FeatureRich",
"build:shard": "tact --config ./tact.config.json --project Shard",
"lint": "tact-fmt --check ./src && yarn misti ./tact.config.json",
"lint:es": "eslint .",
"lint:fix": "eslint . --fix",
"test": "yarn test:base && yarn test:governance && yarn test:feature-rich",
"test": "yarn test:base && yarn test:governance && yarn test:feature-rich && yarn test:shard",
"test:base": "yarn build:base && jest src/tests/base -t 'Base Jetton'",
"test:governance": "yarn build:governance && jest src/tests/governance-tests",
"test:feature-rich": "yarn build:feature-rich && jest src/tests/base -t 'Feature Rich Jetton' && jest src/tests/feature-rich",
"test:shard": "yarn build:shard && jest src/tests/base -t 'Shard' && jest src/tests/shard",
"deploy:base": "ts-node ./src/scripts/base.deploy.ts",
"deploy:governace": "ts-node ./src/scripts/governance.deploy.ts",
"read": "ts-node ./src/scripts/contract.read.ts",
Expand All @@ -32,27 +34,30 @@
"@aws-crypto/sha256-js": "^5.2.0",
"@nowarp/misti": "^0.8.1",
"@orbs-network/ton-access": "^2.3.3",
"@tact-lang/compiler": "^1.6.7",
"@tact-lang/ton-jest": "^0.0.4",
"@ton/core": "^0.60.1",
"@ton/crypto": "^3.2.0",
"@ton/sandbox": "^0.20.0",
"@ton/test-utils": "^0.4.2",
"@ton/ton": "^13.9.0",
"@types/jest": "^29.2.4",
"@types/node": "^18.11.14",
"@types/qs": "^6.9.7",
"base64url": "^3.0.1",
"chalk": "4.1.2",
"cli-table3": "^0.6.5",
"cross-env": "^7.0.3",
"dotenv": "^16.4.5",
"dotenv": "^16.5.0",
"yarn-deduplicate": "^6.0.2",
"@tact-lang/compiler": "1.6.7",
"@tact-lang/deployer": "^0.2.0",
"@tact-lang/ton-abi": "^0.0.3",
"@tact-lang/ton-jest": "^0.0.4",
"@ton/core": "^0.60.1",
"@ton/crypto": "^3.3.0",
"@ton/sandbox": "0.30.0",
"@ton/test-utils": "^0.6.0",
"@ton/ton": "^15.2.1",
"@tondevwallet/traces": "^0.1.6",
"@types/jest": "^29.5.14",
"@types/node": "^22.15.17",
"@types/qs": "^6.9.18",
"base64url": "^3.0.1",
"jest": "^29.3.1",
"qs": "^6.11.0",
"ts-jest": "^29.0.3",
"ts-node": "^10.9.1",
"typescript": "^4.9.4",
"yarn-deduplicate": "^6.0.2"
"typescript": "^4.9.4"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e",
"devDependencies": {
Expand Down
1 change: 1 addition & 0 deletions spell/custom-dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ tonweb
utime
workchain
yada
Toncoins
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sorted lines

223 changes: 223 additions & 0 deletions src/contracts/send.tact
AE8F
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import "./shard-utils";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's group this to separate folder and add comments why we need it, or link the issue with tact-lang PR


asm extends inline fun send(self: DeployParameters) {
// Instructions are grouped, and the stack states they produce as a group are shown right after.
//
// → Stack state
// s0: `params.init.data`
// s1: `params.init.code`
// s2: `params.bounce`
// s3: `params.value`
// s4: `params.body`
// s5: `params.mode`
// For brevity, the "params" prefix will be omitted from now on.

// Group 1: Preparation of needed params
// For almost identical logic and instructions,
// see comments inside `contractHash()` function in contract.tact
4 1 BLKPUSH // pushes 2 copies of `init.code` and `init.data`
HASHCU // `init.data` hash
SWAP
HASHCU // `init.code` hash
SWAP2
CDEPTH // `init.data` depth
SWAP
CDEPTH // `init.code` depth

// Group 2: Calculating destination address
// For almost identical logic and instructions,
// see comments inside `contractHash()` function in contract.tact
131380 INT // (2 << 16) | (1 << 8) | 0x34
NEWC
24 STU
16 STU
16 STU
256 STU
256 STU
ONE HASHEXT_SHA256 // obtains hash part (account id) of the address
// → Stack state
// s0: destAddr(hash part)
// s1: `init.data`
// s2: `init.code`
// s3 and below: `bounce`, `value`, `body`, `mode`

// Group 3: Building a message (CommonMsgInfoRelaxed)
s3 XCHG0 // swaps `bounce` with destAddr(hash part)
NEWC
b{01} STSLICECONST // store tag = $0 and ihr_disabled = true
1 STI // store `bounce`
s1 s2 XCHG // swap `init.data` with `init.code`, placing code on s1
STREF // store `init.code`
STREF // store `init.data`
// Inline StateInit:
b{00010000000000} STSLICECONST
// 0 + 00 + 10 + 0 + 00000000
// 1) 0 - bounced = false
// 2) 00 - src = addr_none
// 3) 10 - tag of addr_std (part of dest)
// 4) 0 - Maybe Anycast = false
// 5) 00000000 - workchain_id (part of dest)
//
256 STU // store destAddr(hash part)
SWAP // Builder on top, `value` below
STGRAMS // store `value`
105 PUSHINT // 1 + 4 + 4 + 64 + 32
STZEROES // store currency_collection, ihr_fee, fwd_fee, created_lt and created_at

// Group 4: Continue building a message (CommonMsgInfoRelaxed into MessageRelaxed)
// Remaining bits of MessageRelaxed:
b{1000110} STSLICECONST
// 10 + 0 + 0 + 1 + 1 + 0
// 10 - Maybe (Either StateInit ^StateInit) = true false
// 0 - split_depth:(Maybe (## 5)) = false
// 0 = special:(Maybe TickTock) = false
// 1 = code:(Maybe ^Cell) = true
// 1 = data:(Maybe ^Cell) = true
// 0 = library:(Maybe ^Cell) = false
//
STDICT // store `body` as ref with an extra Maybe bit, since `body` might be null
ENDC // finalize the message
// → Stack state
// s0: Cell
// s1: params.`mode`

// Group 5: Sending the message, with `mode` on top
SWAP
SENDRAWMSG
}

extends inline fun send(self: ShardDeployParameters) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely need comments here)

let newStateInit = self.deployParameters.init.toShardCell();

let msg = beginCell()
.storeUint(1, 2) // 0 1
.storeBool(self.deployParameters.bounce)
.storeUint(1 << 10, 14) // 0 0 0 1 0 0 0 0 0 0 0 0 0 0
.storeUint(changeAddressHashShard(newStateInit.hash(), self.shard), 256)
.storeCoins(self.deployParameters.value)
.storeUint(2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1)
.storeRef(newStateInit)
.storeMaybeRef(self.deployParameters.body)
.endCell();

sendRawMessage(msg, self.deployParameters.mode);
}

extends inline fun send(self: ShardMessageParameters) {
self.messageParameters.to = changeAddressShard(self.messageParameters.to, self.shard);
self.messageParameters.send();
}

asm extends inline fun send(self: MessageParameters) {
NEWC
b{01} STSLICECONST // store tag = $0 and ihr_disabled = true
1 STI // store `bounce`
b{000} STSLICECONST // store bounced = false and src = addr_none
STSLICE // store `to`
SWAP
STGRAMS // store `value`
106 PUSHINT // 1 + 4 + 4 + 64 + 32 + 1
STZEROES
// → Stack state
// s0: Builder
// s1: `body`
// s2: `mode`
STDICT
ENDC
SWAP
SENDRAWMSG
}

asm extends inline fun send(self: SendParameters) {
// Instructions are grouped, and the stack states they produce as a group are shown right after.
// In the end, our message Cell should have the following TL-B structure:
// message$_ {X:Type}
// info:CommonMsgInfoRelaxed
// init:(Maybe (Either StateInit ^StateInit))
// body:(Either X ^X)
// = MessageRelaxed X;

// → Stack state
// s0: `self.bounce`
// s1: `self.to`
// s2: `self.value`
// s3: `self.data`
// s4: `self.code`
// s5: `self.body`
// s6: `self.mode`
// For brevity, the "self" prefix will be omitted from now on.

// Group 1: Storing the `bounce`, `to` and `value` into a Builder
NEWC
b{01} STSLICECONST // store tag = $0 and ihr_disabled = true
1 STI // store `bounce`
b{000} STSLICECONST // store bounced = false and src = addr_none
STSLICE // store `to`
SWAP
STGRAMS // store `value`
105 PUSHINT // 1 + 4 + 4 + 64 + 32
STZEROES // store currency_collection, ihr_fee, fwd_fee, created_lt and created_at
// → Stack state
// s0: Builder
// s1: `data`
// s2: `code`
// s3: `body`
// s4: `mode`

// Group 2: Placing the Builder after code and data, then checking those for nullability
s2 XCHG0
DUP2
ISNULL
SWAP
ISNULL
AND
// → Stack state
// s0: -1 (true) if `data` and `code` are both null, 0 (false) otherwise
// s1: `code`
// s2: `data`
// s3: Builder
// s4: `body`
// s5: `mode`

// Group 3: Left branch of the IFELSE, executed if s0 is -1 (true)
<{
DROP2 // drop `data` and `code`, since either of those is null
b{0} STSLICECONST
}> PUSHCONT

// Group 3: Right branch of the IFELSE, executed if s0 is 0 (false)
<{
// _ split_depth:(Maybe (## 5))
// special:(Maybe TickTock)
// code:(Maybe ^Cell)
// data:(Maybe ^Cell)
// library:(Maybe ^Cell)
// = StateInit;
ROT // place message Builder on top
b{10} STSLICECONST // store Maybe = true, Either = false
// Start composing inlined StateInit
b{00} STSLICECONST // store split_depth and special first
STDICT // store code
STDICT // store data
b{0} STSLICECONST // store library
}> PUSHCONT

// Group 3: IFELSE that does the branching shown above
IFELSE
// → Stack state
// s0: Builder
// s1: null or StateInit
// s2: `body`
// s3: `mode`

// Group 4: Finalizing the message
STDICT // store `body` as ref with an extra Maybe bit, since `body` might be null
ENDC
// → Stack state
// s0: Cell
// s1: `mode`

// Group 5: Sending the message, with `mode` on top
SWAP
SENDRAWMSG // https://github.com/tact-lang/tact/issues/1558
}
70 changes: 70 additions & 0 deletions src/contracts/shard-utils.tact
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
const prefixLength: Int = 8;

struct ShardDeployParameters {
Copy link
Contributor

Choose a reason for hiding this comment

10000 The reason will be displayed to describe this comment to others. Learn more.

Let's add something like getShardNumber(addr: Address): Int as uint8;

deployParameters: DeployParameters;

/// The shard number to deploy the contract to.
shard: Int as uint8;
}

struct ShardMessageParameters {
messageParameters: MessageParameters;

/// The shard number to deploy the contract to.
shard: Int as uint8;
}

extends inline fun toShard(self: DeployParameters, shard: Int): ShardDeployParameters {
return ShardDeployParameters {
shard,
deployParameters: self,
};
}

extends inline fun toShard(self: MessageParameters, shard: Int): ShardMessageParameters {
return ShardMessageParameters {
shard,
messageParameters: self,
};
}

inline fun changeAddressHashShard(addr_hash: Int, shard: Int): Int {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
inline fun changeAddressHashShard(addr_hash: Int, shard: Int): Int {
inline fun changeAddressHashShard(addrHash: Int, shard: Int): Int {

return (addr_hash & ((1 << (256 - prefixLength)) - 1)) | (shard << (256 - prefixLength));
}

inline fun changeAddressShard(addr: Address, shard: Int): Address {
let sl: Slice = addr.asSlice();

return beginCell().storeUint(sl.loadUint(11), 11).storeUint(changeAddressHashShard(sl.loadUint(256), shard), 256).asSlice().loadAddress();
}

inline fun getShardFromAddress(addr: Slice): Int {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's better to accept Address as a parameter and transform it into a Slice internally

addr.skipBits(11);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add a comment here, why it is 11

return addr.loadUint(prefixLength);
}

extends inline fun toShardCell(self: StateInit): Cell {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idk what is the prorer naming here, but toShardCell is confusing

let newStateInit = beginCell()
.storeUint(32 + prefixLength, 6)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why 6?

.storeUint(6, 4)
.storeRef(self.code)
.storeRef(self.data)
.endCell();

return newStateInit;
}

inline fun contractBasechainAddressShard(s: StateInit): BasechainAddress {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get what this function does from the name

let hash = s.toShardCell().hash();
return newBasechainAddress(hash);
}

inline extends fun hasSameBasechainAddressShard(self: StateInit, sender: Address): Bool {
let senderAddress = parseStdAddress(sender.asSlice()).address;
let baseAddress = contractBasechainAddressShard(self);
return (baseAddress.hash!! & ((1 << (256 - prefixLength)) - 1)) == (senderAddress & ((1 << (256 - prefixLength)) - 1));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you check LSHIFT group of instructions? Maybe some of them allows logical overflow

}

inline extends fun hasSameShard(self: Address, b: Address): Bool {
return getShardFromAddress(self.asSlice()) == getShardFromAddress(b.asSlice());
}
4 changes: 4 additions & 0 deletions src/contracts/shard/constants.tact
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const gasForBurn: Int = 8000;
const gasForTransfer: Int = 11050;
const minTonsForStorage: Int = ton("0.01"); // This should use https://github.com/tact-lang/tact/issues/2336 in the future
const Basechain: Int = 0;
Loading
0