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

fix: various bugs in traits #1591

Merged
merged 22 commits into from
Feb 5, 2025
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
030b3af
fix: support overriding constant and methods of BaseTrait
i582 Jan 27, 2025
f7f3a90
revert comment
i582 Jan 27, 2025
9a8552f
added CHANGELOG.md entry
i582 Jan 27, 2025
439c5a2
remove unnecessary code
i582 Jan 27, 2025
3da1825
missed `;`
i582 Jan 27, 2025
8b61245
Merge branch 'main' into pmakhnev/constants-override-in-traits
i582 Jan 27, 2025
27a2010
Merge branch 'main' into pmakhnev/constants-override-in-traits
i582 Jan 27, 2025
842c1c7
Merge branch 'main' into pmakhnev/constants-override-in-traits
i582 Jan 28, 2025
e72a705
Merge branch 'main' into pmakhnev/constants-override-in-traits
anton-trunov Jan 29, 2025
065723a
Merge branch 'main' into pmakhnev/constants-override-in-traits
anton-trunov Jan 31, 2025
50478e8
Merge branch 'main' into pmakhnev/constants-override-in-traits
i582 Feb 4, 2025
544d342
fix: forbid traits inherit implicitly from BaseTrait
i582 Feb 4, 2025
91ea3ff
added note in documentation
i582 Feb 4, 2025
805d3e6
added entry to CHANGELOG.md
i582 Feb 4, 2025
72cb3a9
review fixes
i582 Feb 4, 2025
ab222e3
Merge branch 'main' into pmakhnev/constants-override-in-traits
i582 Feb 5, 2025
11c883e
Merge branch 'main' into pmakhnev/constants-override-in-traits
i582 Feb 5, 2025
3b351ed
check that the overridden constant has a super constant
i582 Feb 5, 2025
e4e96d2
added entry to CHANGELOG.md
i582 Feb 5, 2025
e254a9c
updated snapshots
i582 Feb 5, 2025
822eb8e
Merge branch 'main' into pmakhnev/constants-override-in-traits
i582 Feb 5, 2025
37bb315
inline isOverride
i582 Feb 5, 2025
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
3 changes: 3 additions & 0 deletions dev-docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Runtime `sha256` now work for arbitrary strings with length >= 128: PR [#1626](https://github.com/tact-lang/tact/pull/1626)
- Generated code in TypeScript wrappers for contract with `init(init: Init)`: PR [#1709](https://github.com/tact-lang/tact/pull/1709)
- Error message for comment (text) receivers with 124 bytes or more: PR [#1711](https://github.com/tact-lang/tact/pull/1711)
- Support overriding constants and methods of BaseTrait: PR [#1591](https://github.com/tact-lang/tact/pull/1591)
- Forbid traits inherit implicitly from BaseTrait: PR [#1591](https://github.com/tact-lang/tact/pull/1591)
- Forbid the `override` modifier for constants without the corresponding super-constant: PR [#1591](https://github.com/tact-lang/tact/pull/1591)

### Docs

Expand Down
8 changes: 7 additions & 1 deletion docs/src/content/docs/ref/core-base.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ prev:
label: "OTP-006: Contract Package"
---

Every [contract](/book/contracts) and [trait](/book/types#traits) in Tact implicitly [inherits](/book/contracts#traits) the `BaseTrait{:tact}` trait, which contains a number of the most useful [internal functions](/book/contracts#internal-functions) for any kind of contract, and a constant `self.storageReserve{:tact}` aimed at advanced users of Tact.
Every [contract](/book/contracts) in Tact implicitly [inherits](/book/contracts#traits) the `BaseTrait{:tact}` trait, which contains a number of the most useful [internal functions](/book/contracts#internal-functions) for any kind of contract, and a constant `self.storageReserve{:tact}` aimed at advanced users of Tact.

:::tip

Prior to 1.6.0, `BaseTrait{:tact}` was also implicitly inherited by traits, but now you must explicitly specify `with BaseTrait{:tact}` for your traits to use it.

:::

## Constants

Expand Down
23 changes: 12 additions & 11 deletions src/stdlib/stdlib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ files["libs/content.tact"] =
"fQ==";
files["libs/deploy.tact"] =
"Cm1lc3NhZ2UgRGVwbG95IHsKICAgIHF1ZXJ5SWQ6IEludCBhcyB1aW50NjQ7Cn0KCm1lc3NhZ2UgRGVwbG95T2sgewogICAgcXVlcnlJZDogSW50IGFzIHVpbnQ2NDsK" +
"fQoKdHJhaXQgRGVwbG95YWJsZSB7CiAgICByZWNlaXZlKGRlcGxveTogRGVwbG95KSB7CiAgICAgICAgc2VsZi5ub3RpZnkoRGVwbG95T2t7cXVlcnlJZDogZGVwbG95" +
"LnF1ZXJ5SWR9LnRvQ2VsbCgpKTsKICAgIH0KfQoKbWVzc2FnZSBGYWN0b3J5RGVwbG95IHsKICAgIHF1ZXJ5SWQ6IEludCBhcyB1aW50NjQ7CiAgICBjYXNoYmFjazog" +
"QWRkcmVzczsKfQoKdHJhaXQgRmFjdG9yeURlcGxveWFibGUgIHsKICAgIHJlY2VpdmUoZGVwbG95OiBGYWN0b3J5RGVwbG95KSB7CiAgICAgICAgc2VsZi5mb3J3YXJk" +
"KGRlcGxveS5jYXNoYmFjaywgRGVwbG95T2t7cXVlcnlJZDogZGVwbG95LnF1ZXJ5SWR9LnRvQ2VsbCgpLCBmYWxzZSwgbnVsbCk7CiAgICB9Cn0=";
"fQoKdHJhaXQgRGVwbG95YWJsZSB3aXRoIEJhc2VUcmFpdCB7CiAgICByZWNlaXZlKGRlcGxveTogRGVwbG95KSB7CiAgICAgICAgc2VsZi5ub3RpZnkoRGVwbG95T2t7" +
"cXVlcnlJZDogZGVwbG95LnF1ZXJ5SWR9LnRvQ2VsbCgpKTsKICAgIH0KfQoKbWVzc2FnZSBGYWN0b3J5RGVwbG95IHsKICAgIHF1ZXJ5SWQ6IEludCBhcyB1aW50NjQ7" +
"CiAgICBjYXNoYmFjazogQWRkcmVzczsKfQoKdHJhaXQgRmFjdG9yeURlcGxveWFibGUgd2l0aCBCYXNlVHJhaXQgIHsKICAgIHJlY2VpdmUoZGVwbG95OiBGYWN0b3J5" +
"RGVwbG95KSB7CiAgICAgICAgc2VsZi5mb3J3YXJkKGRlcGxveS5jYXNoYmFjaywgRGVwbG95T2t7cXVlcnlJZDogZGVwbG95LnF1ZXJ5SWR9LnRvQ2VsbCgpLCBmYWxz" +
"ZSwgbnVsbCk7CiAgICB9Cn0K";
files["libs/dns.fc"] =
"c2xpY2UgZG5zX3N0cmluZ190b19pbnRlcm5hbChzbGljZSBkb21haW4pIGlubGluZV9yZWYgewoKICAgIDs7IFNwZWNpYWwgY2FzZSBmb3Igcm9vdCBkb21haW4KICAg" +
"IGlmICgoZG9tYWluLnNsaWNlX2JpdHMoKSA9PSA4KSAmIChkb21haW4uc2xpY2VfcmVmcygpID09IDApKSB7CiAgICAgICAgaWYgKGRvbWFpbi5wcmVsb2FkX3VpbnQo" +
Expand Down Expand Up @@ -89,13 +90,13 @@ files["libs/dns.tact"] =
files["libs/ownable.tact"] =
"bWVzc2FnZSBDaGFuZ2VPd25lciB7CiAgICBxdWVyeUlkOiBJbnQgYXMgdWludDY0OwogICAgbmV3T3duZXI6IEFkZHJlc3M7Cn0KCm1lc3NhZ2UgQ2hhbmdlT3duZXJP" +
"ayB7CiAgICBxdWVyeUlkOiBJbnQgYXMgdWludDY0OwogICAgbmV3T3duZXI6IEFkZHJlc3M7Cn0KCkBpbnRlcmZhY2UoIm9yZy50b24ub3duYWJsZSIpCnRyYWl0IE93" +
"bmFibGUgewogICAgb3duZXI6IEFkZHJlc3M7CgogICAgZnVuIHJlcXVpcmVPd25lcigpIHsKICAgICAgICBuYXRpdmVUaHJvd1VubGVzcygxMzIsIHNlbmRlcigpID09" +
"IHNlbGYub3duZXIpOwogICAgfQoKICAgIGdldCBmdW4gb3duZXIoKTogQWRkcmVzcyB7CiAgICAgICAgcmV0dXJuIHNlbGYub3duZXI7CiAgICB9Cn0KCkBpbnRlcmZh" +
"Y2UoIm9yZy50b24ub3duYWJsZS50cmFuc2ZlcmFibGUudjIiKQp0cmFpdCBPd25hYmxlVHJhbnNmZXJhYmxlIHdpdGggT3duYWJsZSB7CgogICAgb3duZXI6IEFkZHJl" +
"c3M7CgogICAgcmVjZWl2ZShtc2c6IENoYW5nZU93bmVyKSB7CiAgICAgICAgCiAgICAgICAgLy8gQ2hlY2sgaWYgdGhlIHNlbmRlciBpcyB0aGUgb3duZXIKICAgICAg" +
"ICBzZWxmLnJlcXVpcmVPd25lcigpOwoKICAgICAgICAvLyBVcGRhdGUgb3duZXIKICAgICAgICBzZWxmLm93bmVyID0gbXNnLm5ld093bmVyOwoKICAgICAgICAvLyBS" +
"ZXBseSByZXN1bHQKICAgICAgICBzZWxmLnJlcGx5KENoYW5nZU93bmVyT2t7IHF1ZXJ5SWQ6IG1zZy5xdWVyeUlkLCBuZXdPd25lcjptc2cubmV3T3duZXIgfS50b0Nl" +
"bGwoKSk7CiAgICB9Cn0=";
"bmFibGUgd2l0aCBCYXNlVHJhaXQgewogICAgb3duZXI6IEFkZHJlc3M7CgogICAgZnVuIHJlcXVpcmVPd25lcigpIHsKICAgICAgICBuYXRpdmVUaHJvd1VubGVzcygx" +
"MzIsIHNlbmRlcigpID09IHNlbGYub3duZXIpOwogICAgfQoKICAgIGdldCBmdW4gb3duZXIoKTogQWRkcmVzcyB7CiAgICAgICAgcmV0dXJuIHNlbGYub3duZXI7CiAg" +
"ICB9Cn0KCkBpbnRlcmZhY2UoIm9yZy50b24ub3duYWJsZS50cmFuc2ZlcmFibGUudjIiKQp0cmFpdCBPd25hYmxlVHJhbnNmZXJhYmxlIHdpdGggT3duYWJsZSB7Cgog" +
"ICAgb3duZXI6IEFkZHJlc3M7CgogICAgcmVjZWl2ZShtc2c6IENoYW5nZU93bmVyKSB7CiAgICAgICAgCiAgICAgICAgLy8gQ2hlY2sgaWYgdGhlIHNlbmRlciBpcyB0" +
"aGUgb3duZXIKICAgICAgICBzZWxmLnJlcXVpcmVPd25lcigpOwoKICAgICAgICAvLyBVcGRhdGUgb3duZXIKICAgICAgICBzZWxmLm93bmVyID0gbXNnLm5ld093bmVy" +
"OwoKICAgICAgICAvLyBSZXBseSByZXN1bHQKICAgICAgICBzZWxmLnJlcGx5KENoYW5nZU93bmVyT2t7IHF1ZXJ5SWQ6IG1zZy5xdWVyeUlkLCBuZXdPd25lcjptc2cu" +
"bmV3T3duZXIgfS50b0NlbGwoKSk7CiAgICB9Cn0K";
files["libs/stoppable.tact"] =
"aW1wb3J0ICIuL293bmFibGUiOwoKQGludGVyZmFjZSgib3JnLnRvbi5zdG9wcGFibGUiKQp0cmFpdCBTdG9wcGFibGUgd2l0aCBPd25hYmxlIHsKICAgIAogICAgc3Rv" +
"cHBlZDogQm9vbDsKICAgIG93bmVyOiBBZGRyZXNzOwoKICAgIGZ1biByZXF1aXJlTm90U3RvcHBlZCgpIHsKICAgICAgICByZXF1aXJlKCFzZWxmLnN0b3BwZWQsICJD" +
Expand Down
6 changes: 3 additions & 3 deletions src/stdlib/stdlib/libs/deploy.tact
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ message DeployOk {
queryId: Int as uint64;
}

trait Deployable {
trait Deployable with BaseTrait {
receive(deploy: Deploy) {
self.notify(DeployOk{queryId: deploy.queryId}.toCell());
}
Expand All @@ -18,8 +18,8 @@ message FactoryDeploy {
cashback: Address;
}

trait FactoryDeployable {
trait FactoryDeployable with BaseTrait {
receive(deploy: FactoryDeploy) {
self.forward(deploy.cashback, DeployOk{queryId: deploy.queryId}.toCell(), false, null);
}
}
}
4 changes: 2 additions & 2 deletions src/stdlib/stdlib/libs/ownable.tact
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ message ChangeOwnerOk {
}

@interface("org.ton.ownable")
trait Ownable {
trait Ownable with BaseTrait {
owner: Address;

fun requireOwner() {
Expand Down Expand Up @@ -37,4 +37,4 @@ trait OwnableTransferable with Ownable {
// Reply result
self.reply(ChangeOwnerOk{ queryId: msg.queryId, newOwner:msg.newOwner }.toCell());
}
}
}
36 changes: 36 additions & 0 deletions src/test/e2e-emulated/base-trait-constant-override-1.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { toNano } from "@ton/core";
import { Blockchain, SandboxContract, TreasuryContract } from "@ton/sandbox";
import { TraitsConstantContract } from "./contracts/output/base-trait-constant-override-1_TraitsConstantContract";
import "@ton/test-utils";

describe("base-trait-constant-override-1", () => {
let blockchain: Blockchain;
let treasure: SandboxContract<TreasuryContract>;
let contract: SandboxContract<TraitsConstantContract>;

beforeEach(async () => {
blockchain = await Blockchain.create();
treasure = await blockchain.treasury("treasure");

contract = blockchain.openContract(
await TraitsConstantContract.fromInit(),
);

const deployResult = await contract.send(
treasure.getSender(),
{ value: toNano("0.5") },
null,
);

expect(deployResult.transactions).toHaveTransaction({
from: treasure.address,
to: contract.address,
success: true,
deploy: true,
});
});

it("should override constant correctly", async () => {
expect(await contract.getConstant()).toEqual(100n);
});
});
36 changes: 36 additions & 0 deletions src/test/e2e-emulated/base-trait-constant-override-2.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { toNano } from "@ton/core";
import { Blockchain, SandboxContract, TreasuryContract } from "@ton/sandbox";
import { TraitsConstantContract } from "./contracts/output/base-trait-constant-override-2_TraitsConstantContract";
import "@ton/test-utils";

describe("base-trait-constant-override-2", () => {
let blockchain: Blockchain;
let treasure: SandboxContract<TreasuryContract>;
let contract: SandboxContract<TraitsConstantContract>;

beforeEach(async () => {
blockchain = await Blockchain.create();
treasure = await blockchain.treasury("treasure");

contract = blockchain.openContract(
await TraitsConstantContract.fromInit(),
);

const deployResult = await contract.send(
treasure.getSender(),
{ value: toNano("0.5") },
null,
);

expect(deployResult.transactions).toHaveTransaction({
from: treasure.address,
to: contract.address,
success: true,
deploy: true,
});
});

it("should override constant correctly", async () => {
expect(await contract.getConstant()).toEqual(10000n);
});
});
36 changes: 36 additions & 0 deletions src/test/e2e-emulated/base-trait-function-override.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { toNano } from "@ton/core";
import { Blockchain, SandboxContract, TreasuryContract } from "@ton/sandbox";
import { BaseTraitsFunctionContract } from "./contracts/output/base-trait-function-override_BaseTraitsFunctionContract";
import "@ton/test-utils";

describe("base-trait-function-override", () => {
let blockchain: Blockchain;
let treasure: SandboxContract<TreasuryContract>;
let contract: SandboxContract<BaseTraitsFunctionContract>;

beforeEach(async () => {
blockchain = await Blockchain.create();
treasure = await blockchain.treasury("treasure");

contract = blockchain.openContract(
await BaseTraitsFunctionContract.fromInit(),
);

const deployResult = await contract.send(
treasure.getSender(),
{ value: toNano("10") },
null,
);

expect(deployResult.transactions).toHaveTransaction({
from: treasure.address,
to: contract.address,
success: true,
deploy: true,
});
});

it("should override function correctly", async () => {
expect(await contract.getValue()).toEqual(1000n);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
trait OverrideBaseTraitConstant with BaseTrait {
override const storageReserve: Int = 100;
}

contract TraitsConstantContract with OverrideBaseTraitConstant {
receive() {
// deploy
}

get fun constant(): Int {
return self.storageReserve;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
trait OverrideBaseTraitConstantWithVirtual with BaseTrait {
override virtual const storageReserve: Int = 100;
}

trait OverrideOverridden with OverrideBaseTraitConstantWithVirtual {
override const storageReserve: Int = 10000;
}

contract TraitsConstantContract with OverrideOverridden {
receive() {
// deploy
}

get fun constant(): Int {
return self.storageReserve;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
trait OverrideBaseTraitFunction with BaseTrait {
value: Int;

override fun notify(body: Cell?) {
self.value = 1000;
}
}

contract BaseTraitsFunctionContract with OverrideBaseTraitFunction {
value: Int = 0;

receive() {
// deploy
}

get fun value(): Int {
self.notify(null);
return self.value;
}
}
15 changes: 12 additions & 3 deletions src/test/e2e-emulated/contracts/traits.tact
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
trait Animal {
trait Animal with BaseTrait {
abstract fun getType(): String;
abstract fun getSound(): String;

Expand Down Expand Up @@ -33,7 +33,16 @@ trait Laika with Dog {
}
}

contract LaikaContract with Laika {
// empty contract, we just inherit everything from the traits
trait WithConstant {
virtual const foo: Int = 10;
}

trait IWantToOverrideConstant with WithConstant {
override const foo: Int = 100;
}

contract LaikaContract with Laika, IWantToOverrideConstant {
get fun fooConstant(): Int {
return self.foo;
}
}
5 changes: 5 additions & 0 deletions src/test/e2e-emulated/traits.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,9 @@ describe("traits", () => {
// Check the contract's behavior after deployment
expect(await contract.getSay()).toBe("I am a Laika and I say Woof");
});

it("should override constant correctly", async () => {
// Check the contract's behavior after deployment
expect(await contract.getFooConstant()).toBe(100n);
});
});
Loading