Skip to content

Commit

Permalink
fix: various bugs with constants and BaseTrait inheritance in traits (#…
Browse files Browse the repository at this point in the history
…1591)

* fix: support overriding constant and methods of BaseTrait

* fix: forbid traits inherit implicitly from BaseTrait

* fix: check that the overridden constant has a super constant
  • Loading branch information
i582 authored Feb 5, 2025
1 parent ca634cd commit f853782
Show file tree
Hide file tree
Showing 33 changed files with 8,339 additions and 4,377 deletions.
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;
}
}
20 changes: 20 additions & 0 deletions src/test/e2e-emulated/contracts/base-trait-function-override.tact
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

0 comments on commit f853782

Please sign in to comment.