Skip to content
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

feat: new functions in stdlib from stdlib.fc and math.fc #986

Open
wants to merge 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
19a5f30
feat: add missing functions from `stdlib.fc`
Gusarich Oct 25, 2024
a198af0
feat: add `sgn` math function
Gusarich Oct 25, 2024
165a125
chore: update changelog
Gusarich Oct 25, 2024
7941532
feat: apply suggestions from code review
Gusarich Nov 17, 2024
00790e4
chore: fix varuint comment in stdlib
Gusarich Nov 17, 2024
293c7cc
feat: remove `stringHash` due to existence of `.hash` method
Gusarich Nov 17, 2024
fba75c7
feat: update `stdlib.ts`
Gusarich Nov 17, 2024
f384afc
feat: add `MULRSHIFT` functions
Gusarich Nov 17, 2024
0e3d6b4
feat: update `stdlib.ts`
Gusarich Nov 17, 2024
1db9371
feat: add `sqrt` function
Gusarich Nov 18, 2024
a403986
feat: `sliceLast` -> `lastBits`, and make `computeDataSize` an extend…
Gusarich Nov 18, 2024
364dbf8
chore: add all function names to changelog
Gusarich Nov 18, 2024
30038ca
feat: `addressNone`
Gusarich Nov 18, 2024
58c33b5
feat: rename mulrshift functions
Gusarich Nov 21, 2024
cbd36dc
feat: rewrite sqrt on tact
Gusarich Nov 21, 2024
2aca980
chore: update serialization snapshots
Gusarich Nov 21, 2024
3dcaf2a
chore: update cspell ignorelist
Gusarich Nov 21, 2024
83d69e7
chore: update CHANGELOG.md
Gusarich Nov 22, 2024
e801103
fix: `addressNone` and `computeDataSize` types
Gusarich Nov 28, 2024
b0ec774
chore: add comments with specs for new math functions
Gusarich Nov 28, 2024
4d05722
feat: cover new functions with tests
Gusarich Nov 28, 2024
734f3a0
feat: update stdlib.ts
Gusarich Nov 29, 2024
a2da1a2
chore: typos and extra linkage
novusnota Dec 4, 2024
2b72e66
feat(docs): all math functions and all new non-cell related functions…
novusnota Dec 4, 2024
8453507
feat(docs): new cell-related functions plus minor corrections here an…
novusnota Dec 5, 2024
aa58a35
chore: typo
novusnota Dec 5, 2024
4e73eb4
chore: typo
novusnota Dec 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Docs: `preloadRef` method for the `Slice` type: PR [#1044](https://github.com/tact-lang/tact/pull/1044)
- Docs: added DeDust cookbook: PR [#954](https://github.com/tact-lang/tact/pull/954)
- Docs: described the limit for deeply nested expressions: PR [#1101](https://github.com/tact-lang/tact/pull/1101)
- New functions in stdlib from `stdlib.fc` and `math.fc`: `Builder.depth`, `Slice.skipLastBits`, `Slice.firstBits`, `Slice.lastBits`, `Slice.depth`, `Cell.computeDataSize`, `Slice.computeDataSize`, `Cell.depth`, `curLt`, `blockLt`, `setGasLimit`, `getSeed`, `setSeed`, `myCode`, `sign`, `divc`, `muldivc`, `mulShiftRight`, `mulShiftRightRound`, `mulShiftRightCeil`, `sqrt`, `addressNone`: PR [#986](https://github.com/tact-lang/tact/pull/986)

### Changed

Expand Down
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"minmax",
"mintable",
"mktemp",
"muldivc",
"multiformats",
"nanotons",
"Neovim",
Expand Down
118 changes: 74 additions & 44 deletions src/imports/stdlib.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,39 @@ exports[`local-type-inference should automatically set types for let statements
},
],
"types": [
{
"fields": [
{
"name": "cells",
"type": {
"format": 257,
"kind": "simple",
"optional": false,
"type": "int",
},
},
{
"name": "bits",
"type": {
"format": 257,
"kind": "simple",
"optional": false,
"type": "int",
},
},
{
"name": "refs",
"type": {
"format": 257,
"kind": "simple",
"optional": false,
"type": "int",
},
},
],
"header": null,
"name": "DataSize",
},
{
"fields": [
{
Expand Down
28 changes: 28 additions & 0 deletions src/test/e2e-emulated/contracts/math.tact
Copy link
Member

Choose a reason for hiding this comment

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

do we want to also add muldiv?

Original file line number Diff line number Diff line change
Expand Up @@ -426,4 +426,32 @@ contract MathTester with Deployable {
a >>= b;
return a;
}

get fun sign(x: Int): Int {
return sign(x);
}

get fun divc(x: Int, y: Int): Int {
return divc(x, y);
}

get fun muldivc(x: Int, y: Int, z: Int): Int {
return muldivc(x, y, z);
}

get fun mulShiftRight(x: Int, y: Int, z: Int): Int {
return mulShiftRight(x, y, z);
}

get fun mulShiftRightRound(x: Int, y: Int, z: Int): Int {
return mulShiftRightRound(x, y, z);
}

get fun mulShiftRightCeil(x: Int, y: Int, z: Int): Int {
return mulShiftRightCeil(x, y, z);
}

get fun sqrt(x: Int): Int {
return sqrt(x);
}
}
68 changes: 67 additions & 1 deletion src/test/e2e-emulated/contracts/stdlib.tact
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,70 @@ contract StdlibTest {
get fun parseVarAddress(slice: Slice): VarAddress {
return parseVarAddress(slice);
}
}

get fun builderDepth(bl: Builder): Int {
return bl.depth();
}

get fun skipLastBits(sc: Slice, n: Int): Slice {
return sc.skipLastBits(n);
}

get fun firstBits(sc: Slice, n: Int): Slice {
return sc.firstBits(n);
}

get fun lastBits(sc: Slice, n: Int): Slice {
return sc.lastBits(n);
}

get fun sliceDepth(sc: Slice): Int {
return sc.depth();
}

get fun addressNone(): Address? {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why does it return "Maybe Address" and not "Address" ?

Copy link
Member Author

Choose a reason for hiding this comment

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

Because the way Tact currently work with addresses is that "null" as an address is actually addressNone

Copy link
Contributor

@Shvandre Shvandre Dec 1, 2024

Choose a reason for hiding this comment

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

Well, I checked the sources and you are 100% right, but maybe we should change it?
Or change the tlb schemes generation, because

struct Test {
    my_addr: Address?;
}

results

## Test
TLB: `_ my_addr:Maybe address = Test`
Signature: `Test{my_addr:Maybe address}`

In .md file (and maybe in some other places)

Copy link
Member Author

Choose a reason for hiding this comment

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

it's an old problem in Tact, which was blocking multiple issues in the past. I remember some old comments in address-related issues about that.

My opinion is that we should rework addresses, because currently the way they work is not intuitive and doesn't fit into how other types work.
Basically, addr_none should be a valid part of Address type and should be checked by devs against addressNone() (and not null). And Address? should mean Maybe Address, which also could contain addr_none.
@anton-trunov what do you think?

Copy link
Member

Choose a reason for hiding this comment

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

addr_none should be a valid part of Address type and should be checked by devs against addressNone() (and not null).

yeah, this is how it should work, indeed

And Address? should mean Maybe Address

also agreed

which also could contain addr_none.

this one I'm not sure about, I'd say addr_none in the optional version should be 000, what do you think?

Copy link
Member Author

@Gusarich Gusarich Dec 2, 2024

Choose a reason for hiding this comment

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

this one I'm not sure about, I'd say addr_none in the optional version should be 000, what do you think?

well yes, that's what I meant. I was talking about the possibility of the case when value of Address? is equal to addressNone().

Copy link
Member

Choose a reason for hiding this comment

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

@Gusarich Maybe you could take care of this change first (allowing addr_none as an inhabitant of the Address type) and then we merge this PR with the corresponding changes

Copy link
Member

Choose a reason for hiding this comment

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

We could also probably add a config option to turn off address verification along the way

return addressNone();
}

get fun computeDataSizeCell(c: Cell, maxCells: Int): DataSize {
return c.computeDataSize(maxCells);
}

get fun computeDataSizeSlice(sc: Slice, maxCells: Int): DataSize {
return sc.computeDataSize(maxCells);
}

get fun cellDepth(c: Cell): Int {
return c.depth();
}

get fun curLt(): Int {
return curLt();
}

get fun blockLt(): Int {
return blockLt();
}

get fun setGasLimit(gl: Int): Int {
setGasLimit(gl);
let x = 0;
repeat (100) {
x += 1;
}
Shvandre marked this conversation as resolved.
Show resolved Hide resolved
return gasConsumed();
}

get fun getSeed(): Int {
return getSeed();
}

get fun setSeed(seed: Int): Int {
setSeed(seed);
return getSeed();
}

get fun myCode(): Cell {
return myCode();
}
}
58 changes: 58 additions & 0 deletions src/test/e2e-emulated/math.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -507,5 +507,63 @@ describe("math", () => {
expect(await contract.getAugmentedShiftRight(2n, 1n)).toBe(1n);
expect(await contract.getAugmentedShiftRight(4n, 2n)).toBe(1n);
expect(await contract.getAugmentedShiftRight(16n, 3n)).toBe(2n);

// sign
expect(await contract.getSign(123n)).toBe(1n);
expect(await contract.getSign(-123n)).toBe(-1n);
expect(await contract.getSign(0n)).toBe(0n);

// divc: ceil(x/y)
expect(await contract.getDivc(123n, 123n)).toBe(1n);
expect(await contract.getDivc(123n, 124n)).toBe(1n);
expect(await contract.getDivc(0n, 123n)).toBe(0n);
expect(await contract.getDivc(123n, 122n)).toBe(2n);

// muldivc: ceil(x*y/z)
expect(await contract.getMuldivc(123n, 123n, 123n)).toBe(123n);
expect(await contract.getMuldivc(100n, 100n, 10001n)).toBe(1n);
expect(await contract.getMuldivc(100n, 100n, 10000n)).toBe(1n);
expect(await contract.getMuldivc(100n, 100n, 9999n)).toBe(2n);
expect(await contract.getMuldivc(100n, 0n, 1n)).toBe(0n);

// mulShiftRight: floor(x*y/2^z)
expect(await contract.getMulShiftRight(2n, 4n, 3n)).toBe(1n);
expect(await contract.getMulShiftRight(2n, 4n, 2n)).toBe(2n);
expect(await contract.getMulShiftRight(2n, 4n, 1n)).toBe(4n);
expect(await contract.getMulShiftRight(2n, 4n, 0n)).toBe(8n);
expect(await contract.getMulShiftRight(1n, 6n, 0n)).toBe(6n);
expect(await contract.getMulShiftRight(1n, 6n, 3n)).toBe(0n);

// mulShiftRightRound: round(x*y/2^z)
expect(await contract.getMulShiftRightRound(2n, 4n, 3n)).toBe(1n);
expect(await contract.getMulShiftRightRound(2n, 4n, 2n)).toBe(2n);
expect(await contract.getMulShiftRightRound(2n, 4n, 1n)).toBe(4n);
expect(await contract.getMulShiftRightRound(2n, 4n, 0n)).toBe(8n);
expect(await contract.getMulShiftRightRound(1n, 6n, 0n)).toBe(6n);
expect(await contract.getMulShiftRightRound(1n, 4n, 3n)).toBe(1n);
expect(await contract.getMulShiftRightRound(1n, 3n, 3n)).toBe(0n);

// mulShiftRightCeil: ceil(x*y/2^z)
expect(await contract.getMulShiftRightCeil(2n, 4n, 3n)).toBe(1n);
expect(await contract.getMulShiftRightCeil(2n, 4n, 2n)).toBe(2n);
expect(await contract.getMulShiftRightCeil(2n, 4n, 1n)).toBe(4n);
expect(await contract.getMulShiftRightCeil(2n, 4n, 0n)).toBe(8n);
expect(await contract.getMulShiftRightCeil(1n, 6n, 0n)).toBe(6n);
expect(await contract.getMulShiftRightCeil(1n, 7n, 3n)).toBe(1n);
expect(await contract.getMulShiftRightCeil(1n, 4n, 3n)).toBe(1n);
expect(await contract.getMulShiftRightCeil(1n, 3n, 3n)).toBe(1n);

// sqrt: round(sqrt(num))
for (let num = 0n; num <= 100n; num++) {
// console.log(`sqrt(${num}) = ${await contract.getSqrt(num)}`);
expect(await contract.getSqrt(num)).toBe(
BigInt(Math.round(Math.sqrt(Number(num)))),
);
}
for (let num = -3n; num < 0n; num++) {
await expect(contract.getSqrt(num)).rejects.toThrow(
"Unable to execute get method. Got exit_code: 5",
);
}
});
});
62 changes: 60 additions & 2 deletions src/test/e2e-emulated/stdlib.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Address, beginCell, toNano } from "@ton/core";
import { Address, beginCell, Cell, toNano } from "@ton/core";
import { Blockchain, SandboxContract, TreasuryContract } from "@ton/sandbox";
import { StdlibTest } from "./contracts/output/stdlib_StdlibTest";
import "@ton/test-utils";
Expand Down Expand Up @@ -56,7 +56,7 @@ describe("stdlib", () => {
.toString(),
).toBe(beginCell().storeBit(true).endCell().toString());

expect(await contract.getTvm_2023_07Upgrade()).toEqual(1355n);
expect(await contract.getTvm_2023_07Upgrade()).toEqual(1455n);
expect(await contract.getTvm_2024_04Upgrade()).toEqual(82009144n);

expect(
Expand Down Expand Up @@ -106,5 +106,63 @@ describe("stdlib", () => {
expect(addrVar.address.asCell()).toEqualCell(
beginCell().storeUint(345, 123).endCell(),
);

expect(await contract.getBuilderDepth(beginCell())).toBe(0n);
expect(
await contract.getBuilderDepth(beginCell().storeRef(Cell.EMPTY)),
).toBe(1n);

expect(await contract.getSkipLastBits(slice, 1n)).toEqualSlice(
beginCell()
.storeBit(1)
.storeRef(beginCell().storeBit(1).endCell())
.endCell()
.asSlice(),
);

expect(await contract.getFirstBits(slice, 1n)).toEqualSlice(
beginCell().storeBit(1).endCell().asSlice(),
);

expect(await contract.getLastBits(slice, 1n)).toEqualSlice(
beginCell().storeBit(1).endCell().asSlice(),
);

expect(await contract.getSliceDepth(slice)).toBe(1n);

expect(await contract.getAddressNone()).toEqual(null);

expect(
await contract.getComputeDataSizeCell(slice.asCell(), 1000n),
).toMatchObject({
$$type: "DataSize",
cells: 2n,
bits: 3n,
refs: 1n,
});

expect(
await contract.getComputeDataSizeSlice(slice, 1000n),
).toMatchObject({
$$type: "DataSize",
cells: 1n, // -1 for slice
bits: 3n,
refs: 1n,
});

expect(await contract.getCellDepth(slice.asCell())).toBe(1n);

expect(await contract.getCurLt()).toBe(0n);

expect(await contract.getBlockLt()).toBe(0n);

expect(await contract.getSetGasLimit(5000n)).toBe(3997n); // 5000 just to make sure it's enough, 3997 is how much it actually costs
await expect(contract.getSetGasLimit(3996n)).rejects.toThrow("-14"); // 3996 gas is not enough for sure

expect(await contract.getGetSeed()).toBe(0n);

expect(await contract.getSetSeed(123n)).toBe(123n);

expect(await contract.getMyCode()).toEqualCell(contract.init!.code);
});
});
25 changes: 25 additions & 0 deletions stdlib/std/cells.tact
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ asm extends fun refs(self: Builder): Int { BREFS }

asm extends fun bits(self: Builder): Int { BBITS }

asm extends fun depth(self: Builder): Int { BDEPTH }

//
// Slice
//
Expand Down Expand Up @@ -167,6 +169,14 @@ asm extends mutates fun skipBits(self: Slice, l: Int) { SDSKIPFIRST }

asm extends fun endParse(self: Slice) { ENDS }

asm extends fun skipLastBits(self: Slice, len: Int): Slice { SDSKIPLAST }

asm extends fun firstBits(self: Slice, len: Int): Slice { SDCUTFIRST }

asm extends fun lastBits(self: Slice, len: Int): Slice { SDCUTLAST }

asm extends fun depth(self: Slice): Int { SDEPTH }

//
// Slice size
//
Expand Down Expand Up @@ -210,3 +220,18 @@ inline fun emptyCell(): Cell {
inline fun emptySlice(): Slice {
return emptyCell().asSlice();
}

asm fun addressNone(): Address? { b{00} PUSHSLICE }
Shvandre marked this conversation as resolved.
Show resolved Hide resolved

struct DataSize {
cells: Int;
bits: Int;
refs: Int;
}

asm extends fun computeDataSize(self: Cell, maxCells: Int): DataSize { CDATASIZE }

asm extends fun computeDataSize(self: Slice, maxCells: Int): DataSize { SDATASIZE }

asm extends fun depth(self: Cell): Int { CDEPTH }

12 changes: 12 additions & 0 deletions stdlib/std/contract.tact
Original file line number Diff line number Diff line change
Expand Up @@ -240,3 +240,15 @@ asm fun parseStdAddress(slice: Slice): StdAddress { REWRITESTDADDR }
/// See: https://docs.tact-lang.org/ref/core-advanced#parsevaraddress
///
asm fun parseVarAddress(slice: Slice): VarAddress { REWRITEVARADDR }

asm fun curLt(): Int { LTIME }

asm fun blockLt(): Int { BLOCKLT }

asm fun setGasLimit(limit: Int) { SETGASLIMIT }

asm fun getSeed(): Int { RANDSEED }

asm fun setSeed(seed: Int) { SETRAND }

asm fun myCode(): Cell { MYCODE }
Loading