Skip to content

Commit

Permalink
feat: implicit init function if not present (#167)
Browse files Browse the repository at this point in the history
  • Loading branch information
Gusarich authored Mar 21, 2024
1 parent 729d3f6 commit 7c8fd86
Show file tree
Hide file tree
Showing 19 changed files with 1,884 additions and 9 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Update the `dump` function to handle addresses: PR [#175](https://github.com/tact-lang/tact/pull/175)

- The implicit empty init function is now present by default in the contract if not declared

### Fixed

## [1.2.0] - 2024-02-29
Expand Down
109 changes: 109 additions & 0 deletions src/test/__snapshots__/feature-implicit-init.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`feature-send should deploy 1`] = `
[
{
"$seq": 0,
"events": [
{
"$type": "deploy",
},
{
"$type": "received",
"message": {
"body": {
"type": "known",
"value": {
"$$type": "Deploy",
"queryId": 0n,
},
},
"bounce": true,
"from": "@treasure(treasure)",
"to": "kQDHqMJCGk6QhukOD8ZKGLou9KVeRAM3pGOa_JYP47mob9WO",
"type": "internal",
"value": "1",
},
},
{
"$type": "processed",
"gasUsed": 7147n,
},
{
"$type": "sent",
"messages": [
{
"body": {
"type": "known",
"value": {
"$$type": "DeployOk",
"queryId": 0n,
},
},
"bounce": false,
"from": "kQDHqMJCGk6QhukOD8ZKGLou9KVeRAM3pGOa_JYP47mob9WO",
"to": "@treasure(treasure)",
"type": "internal",
"value": "0.991657",
},
],
},
],
},
]
`;

exports[`feature-send should increment counter 1`] = `
[
{
"$seq": 1,
"events": [
{
"$type": "received",
"message": {
"body": {
"text": "increment",
"type": "text",
},
"bounce": true,
"from": "@treasure(treasure)",
"to": "kQDHqMJCGk6QhukOD8ZKGLou9KVeRAM3pGOa_JYP47mob9WO",
"type": "internal",
"value": "1",
},
},
{
"$type": "processed",
"gasUsed": 3746n,
},
],
},
{
"$seq": 2,
"events": [
{
"$type": "storage-charged",
"amount": "0.000000004",
},
{
"$type": "received",
"message": {
"body": {
"text": "increment",
"type": "text",
},
"bounce": true,
"from": "@treasure(treasure)",
"to": "kQDHqMJCGk6QhukOD8ZKGLou9KVeRAM3pGOa_JYP47mob9WO",
"type": "internal",
"value": "1",
},
},
{
"$type": "processed",
"gasUsed": 3746n,
},
],
},
]
`;
75 changes: 75 additions & 0 deletions src/test/feature-implicit-init.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { toNano } from '@ton/core';
import { ContractSystem } from '@tact-lang/emulator';
import { __DANGER_resetNodeId } from '../grammar/ast';
import { MyContract } from './features/output/implicit-init_MyContract';
import { run } from '../node';
import { consoleLogger } from '../logger';

describe('feature-send', () => {
beforeAll(() => {
jest.spyOn(consoleLogger, 'error').mockImplementation(() => {});
});

beforeEach(() => {
__DANGER_resetNodeId();
});

afterAll(() => {
(consoleLogger.error as jest.Mock).mockRestore();
});

afterEach(() => {
(consoleLogger.error as jest.Mock).mockClear();
});

it('should deploy', async () => {
// Init
const system = await ContractSystem.create();
const treasure = system.treasure('treasure');
const contract = system.open(await MyContract.fromInit());
const tracker = system.track(contract.address);
await contract.send(
treasure,
{ value: toNano('1') },
{ $$type: 'Deploy', queryId: 0n }
);
await system.run();
expect(await contract.getGetCounter()).toBe(0n);
expect(tracker.collect()).toMatchSnapshot();
});

it('should increment counter', async () => {
// Init
const system = await ContractSystem.create();
const treasure = system.treasure('treasure');
const contract = system.open(await MyContract.fromInit());
await contract.send(
treasure,
{ value: toNano('1') },
{ $$type: 'Deploy', queryId: 0n }
);
await system.run();

// Test
expect(await contract.getGetCounter()).toBe(0n);
const tracker = system.track(contract);
await contract.send(treasure, { value: toNano('1') }, 'increment');
await system.run();
expect(await contract.getGetCounter()).toBe(1n);
await contract.send(treasure, { value: toNano('1') }, 'increment');
await system.run();
expect(await contract.getGetCounter()).toBe(2n);
expect(tracker.collect()).toMatchSnapshot();
});

it('should not compile with uninitialized storage fields', async () => {
const result = await run({
configPath: __dirname + '/test-tact.config.json',
projectNames: ['implicit-init-2'],
});
expect((consoleLogger.error as jest.Mock).mock.lastCall[0]).toContain(
'Field test_field is not set'
);
expect(result).toBe(false);
});
});
18 changes: 18 additions & 0 deletions src/test/features/implicit-init-2.tact
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import "@stdlib/deploy";

contract MyContract with Deployable {
counter: Int = 0;
test_field: Int;

receive("increment") {
self.counter += 1;
}

get fun getCounter(): Int {
return self.counter;
}

get fun getTestField(): Int {
return self.test_field;
}
}
13 changes: 13 additions & 0 deletions src/test/features/implicit-init.tact
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import "@stdlib/deploy";

contract MyContract with Deployable {
counter: Int = 0;

receive("increment") {
self.counter += 1;
}

get fun getCounter(): Int {
return self.counter;
}
}
1 change: 1 addition & 0 deletions src/test/features/output/implicit-init_MyContract.abi
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"name":"MyContract","types":[{"name":"StateInit","header":null,"fields":[{"name":"code","type":{"kind":"simple","type":"cell","optional":false}},{"name":"data","type":{"kind":"simple","type":"cell","optional":false}}]},{"name":"Context","header":null,"fields":[{"name":"bounced","type":{"kind":"simple","type":"bool","optional":false}},{"name":"sender","type":{"kind":"simple","type":"address","optional":false}},{"name":"value","type":{"kind":"simple","type":"int","optional":false,"format":257}},{"name":"raw","type":{"kind":"simple","type":"slice","optional":false}}]},{"name":"SendParameters","header":null,"fields":[{"name":"bounce","type":{"kind":"simple","type":"bool","optional":false}},{"name":"to","type":{"kind":"simple","type":"address","optional":false}},{"name":"value","type":{"kind":"simple","type":"int","optional":false,"format":257}},{"name":"mode","type":{"kind":"simple","type":"int","optional":false,"format":257}},{"name":"body","type":{"kind":"simple","type":"cell","optional":true}},{"name":"code","type":{"kind":"simple","type":"cell","optional":true}},{"name":"data","type":{"kind":"simple","type":"cell","optional":true}}]},{"name":"Deploy","header":2490013878,"fields":[{"name":"queryId","type":{"kind":"simple","type":"uint","optional":false,"format":64}}]},{"name":"DeployOk","header":2952335191,"fields":[{"name":"queryId","type":{"kind":"simple","type":"uint","optional":false,"format":64}}]},{"name":"FactoryDeploy","header":1829761339,"fields":[{"name":"queryId","type":{"kind":"simple","type":"uint","optional":false,"format":64}},{"name":"cashback","type":{"kind":"simple","type":"address","optional":false}}]}],"receivers":[{"receiver":"internal","message":{"kind":"text","text":"increment"}},{"receiver":"internal","message":{"kind":"typed","type":"Deploy"}}],"getters":[{"name":"getCounter","arguments":[],"returnType":{"kind":"simple","type":"int","optional":false,"format":257}}],"errors":{"2":{"message":"Stack undeflow"},"3":{"message":"Stack overflow"},"4":{"message":"Integer overflow"},"5":{"message":"Integer out of expected range"},"6":{"message":"Invalid opcode"},"7":{"message":"Type check error"},"8":{"message":"Cell overflow"},"9":{"message":"Cell underflow"},"10":{"message":"Dictionary error"},"13":{"message":"Out of gas error"},"32":{"message":"Method ID not found"},"34":{"message":"Action is invalid or not supported"},"37":{"message":"Not enough TON"},"38":{"message":"Not enough extra-currencies"},"128":{"message":"Null reference exception"},"129":{"message":"Invalid serialization prefix"},"130":{"message":"Invalid incoming message"},"131":{"message":"Constraints error"},"132":{"message":"Access denied"},"133":{"message":"Contract stopped"},"134":{"message":"Invalid argument"},"135":{"message":"Code of a contract was not found"},"136":{"message":"Invalid address"},"137":{"message":"Masterchain support is not enabled for this contract"}},"interfaces":["org.ton.introspection.v0","org.ton.abi.ipfs.v0","org.ton.deploy.lazy.v0","org.ton.debug.v0","org.ton.chain.workchain.v0"]}
Binary file not shown.
127 changes: 127 additions & 0 deletions src/test/features/output/implicit-init_MyContract.code.fc
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#pragma version =0.4.3;
#pragma allow-post-modification;
#pragma compute-asm-ltr;

#include "implicit-init_MyContract.headers.fc";
#include "implicit-init_MyContract.stdlib.fc";
#include "implicit-init_MyContract.storage.fc";

;;
;; Contract MyContract functions
;;

(int) $MyContract$_contract_init() impure inline_ref {
var (($self'counter)) = (0);
return ($self'counter);
}

((int), int) $MyContract$_fun_getCounter((int) $self) impure inline_ref {
var (($self'counter)) = $self;
return (($self'counter), $self'counter);
}

;;
;; Receivers of a Contract MyContract
;;

((int), ()) $MyContract$_internal_text_c4f8d72312edfdef5b7bec7833bdbb162d1511bd78a912aed0f2637af65572ae((int) $self) impure inline {
var ($self'counter) = $self;
$self'counter = $self'counter + 1;
return (($self'counter), ());
}

(((int)), ()) $MyContract$_internal_binary_Deploy((int) $self, (int) $deploy) impure inline {
var ($self'counter) = $self;
var ($deploy'queryId) = $deploy;
($self'counter)~$MyContract$_fun_notify($DeployOk$_store_cell($DeployOk$_constructor_queryId($deploy'queryId)));
return (($self'counter), ());
}

;;
;; Get methods of a Contract MyContract
;;

_ %getCounter() method_id(103307) {
var self = $MyContract$_contract_load();
var res = self~$MyContract$_fun_getCounter();
return res;
}

_ supported_interfaces() method_id {
return (
"org.ton.introspection.v0"H >> 128,
"org.ton.abi.ipfs.v0"H >> 128,
"org.ton.deploy.lazy.v0"H >> 128,
"org.ton.debug.v0"H >> 128,
"org.ton.chain.workchain.v0"H >> 128
);
}

_ get_abi_ipfs() method_id {
return "ipfs://QmUMNpuudc6g5LXLYu1TqFHXUkx7rQtuUPpLjexwCKH6R9";
}

_ lazy_deployment_completed() method_id {
return get_data().begin_parse().load_int(1);
}

;;
;; Routing of a Contract MyContract
;;

((int), int) $MyContract$_contract_router_internal((int) self, int msg_bounced, slice in_msg) impure inline_ref {
;; Handle bounced messages
if (msg_bounced) {
return (self, true);
}

;; Parse incoming message
int op = 0;
if (slice_bits(in_msg) >= 32) {
op = in_msg.preload_uint(32);
}


;; Receive Deploy message
if (op == 2490013878) {
var msg = in_msg~$Deploy$_load();
self~$MyContract$_internal_binary_Deploy(msg);
return (self, true);
}

;; Text Receivers
if (op == 0) {
var text_op = slice_hash(in_msg);

;; Receive "increment" message
if (text_op == 0xc4f8d72312edfdef5b7bec7833bdbb162d1511bd78a912aed0f2637af65572ae) {
self~$MyContract$_internal_text_c4f8d72312edfdef5b7bec7833bdbb162d1511bd78a912aed0f2637af65572ae();
return (self, true);
}
}

return (self, false);
}

() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure {

;; Context
var cs = in_msg_cell.begin_parse();
var msg_flags = cs~load_uint(4);
var msg_bounced = -(msg_flags & 1);
slice msg_sender_addr = __tact_verify_address(cs~load_msg_addr());
__tact_context = (msg_bounced, msg_sender_addr, msg_value, cs);
__tact_context_sender = msg_sender_addr;

;; Load contract data
var self = $MyContract$_contract_load();

;; Handle operation
int handled = self~$MyContract$_contract_router_internal(msg_bounced, in_msg);

;; Throw if not handled
throw_unless(130, handled);

;; Persist state
$MyContract$_contract_store(self);
}
Loading

0 comments on commit 7c8fd86

Please sign in to comment.