Skip to content

Commit

Permalink
Ping pong
Browse files Browse the repository at this point in the history
  • Loading branch information
fabcotech committed Jul 17, 2024
1 parent be18272 commit f884239
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 57 deletions.
25 changes: 18 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
## TON (The Open Network) blockchain - The duck repo

![state of the workflow](https://github.com/fabcotech/nest-typeorm-boilerplate/actions/workflows/main.yml/badge.svg)
![state of the workflow](https://github.com/fabcotech/ton-func-contracts/actions/workflows/main.yml/badge.svg)

![The duck](https://sl.combot.org/utyaduck/webp/6xf09f98b3.webp)

A set of low, high value TON blockchain (The Open Network) FunC smart contracts and test suites. New code coming in regularly.

- **Arithmetic** : TON Smart contract that performs basic add/substract/multiply operations on a integer
- **Fibonacci** : TON Smart contract that stores two integers, and continues the fibonacci sequence everytime it is touched.
- **Ten thousands transfers** : Two blockchain users exchange TON back and forth, 10.000 times, balance is checked after each transfer.
- **One thousand token transfer** : Mints a new token with 100B supply, 30B are sent to three blockchain users, they do one thousand small token transfers. Balance is checked after each transfer.
A set of low, high value TON blockchain (The Open Network) FunC smart contracts
and test suites. New code coming in regularly.

- **Arithmetic** : TON Smart contract that performs basic add/substract/multiply
operations on a integer
- **Fibonacci** : TON Smart contract that stores two integers, and continues the
fibonacci sequence everytime it is touched.
- **Ten thousands transfers** : Two blockchain users exchange TON back and
forth, 10.000 times, balance is checked after each transfer.
- **One thousand token transfer** : Mints a new token with 100B supply, 30B are
sent to three blockchain users, they do one thousand small token transfers.
Balance is checked after each transfer.
- **Ping pong** : A contract stores a string (as `func slice`), each time it is
called, `"ping"` is toggled to `"pong"` and vice versa.

```sh
yarn
Expand All @@ -27,4 +35,7 @@ yarn test tests/TenThousandsTransfers.spec.ts

# Only test 1.000 token transfers
yarn test tests/OneThousandTokenTransfers.spec.ts

# Only test ping pong
yarn test tests/PingPong.spec.ts
```
84 changes: 34 additions & 50 deletions contracts/pingpong.fc
Original file line number Diff line number Diff line change
@@ -1,64 +1,48 @@
#include "imports/stdlib.fc";

const op::toggle = "op::toggle"c;
int slice_hash(slice s) asm "HASHSU";
int equal_slices(slice a, slice b) asm "SDEQ";

;; storage variables
global cell ctx_str;

;; id is required to be able to create different instances of counters
;; since addresses in TON depend on the initial state of the contract
global slice str = "ping";

;; load_data populates storage variables using stored data
() load_data() impure {
var ds = get_data().begin_parse();
str = ds~load_slice();
ds.end_parse();
var ds = get_data().begin_parse();
ctx_str = ds~load_ref();
ds.end_parse();
}

;; save_data stores storage variables as a cell into persistent storage
() save_data() impure {
set_data(
begin_cell()
.store_slice(str)
.end_cell()
);
set_data(
begin_cell()
.store_ref(ctx_str)
.end_cell()
);
}

;; recv_internal is the main function of the contract and is called when it receives a message from other contracts
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
if (in_msg_body.slice_empty?()) { ;; ignore all empty messages
return ();
}

slice cs = in_msg_full.begin_parse();
int flags = cs~load_uint(4);
if (flags & 1) { ;; ignore all bounced messages
return ();
}

load_data(); ;; here we populate the storage variables

int op = in_msg_body~load_uint(32); ;; by convention, the first 32 bits of incoming message is the op
int query_id = in_msg_body~load_uint(64); ;; also by convention, the next 64 bits contain the "query id", although this is not always the case

if (op == op::increase) {
slice str = = begin_cell()
.store_slice("pong")
.store_ref(null())
.end_cell()
.begin_parse();
save_data();
return ();
}

throw(0xffff); ;; if the message contains an op that is not known to this contract, we throw
if (in_msg_body.slice_empty?()) {
return ();
}

load_data();
slice current = ctx_str.begin_parse();
slice ping = begin_cell().store_slice("ping").end_cell().begin_parse();

if (equal_slices(current,ping) == -1) {
cell c = begin_cell().store_slice("pong").end_cell();
ctx_str = c;
save_data();
return ();
} else {
cell c = begin_cell().store_slice("ping").end_cell();
ctx_str = c;
save_data();
return ();
}
}

;; get methods are a means to conveniently read contract data using, for example, HTTP APIs
;; they are marked with method_id
;; note that unlike in many other smart contract VMs, get methods cannot be called by other contracts

int get_str() method_id {
load_data();
return str;
}
cell get_str() method_id {
load_data();
return ctx_str;
}
75 changes: 75 additions & 0 deletions tests/PingPong.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox';
import { Cell, toNano, ContractProvider } from '@ton/core';
import '@ton/test-utils';
import { compile } from '@ton/blueprint';

import { PingPong } from '../wrappers/PingPong';

export const Opcodes = {
toggle: 0x7e8764ef,
};

describe('[PingPong]', () => {
let code: Cell;
let blockchain: Blockchain;
let deployer: SandboxContract<TreasuryContract>;
let pingPongContract: SandboxContract<PingPong>;
let user1: null | SandboxContract<TreasuryContract> = null;
let provider: null | ContractProvider = null;

beforeAll(async () => {
code = await compile('PingPong');
blockchain = await Blockchain.create();
user1 = await blockchain.treasury('user1');
pingPongContract = blockchain.openContract(
PingPong.createFromConfig({}, code)
);
provider = blockchain.provider(pingPongContract.address);
deployer = await blockchain.treasury('deployer');
(provider as any).blockchain.openContract(
PingPong.createFromConfig({}, code)
);
});

it('[PingPong] deploys', async () => {
const deployResult = await pingPongContract.sendDeploy(
deployer.getSender(),
toNano('0.05')
);
expect(deployResult.transactions).toHaveTransaction({
from: deployer.address,
to: pingPongContract.address,
deploy: true,
success: true,
});
});

it('[PingPong] toggle', async () => {
const str = await pingPongContract.getStr();
const strOk = (str as any).toString().replace('x{', '').replace('}', '');
const decoded = Buffer.from(strOk, 'hex').toString('utf8');
expect(decoded).toBe('ping');

await pingPongContract.sendToggle(
(user1 as SandboxContract<TreasuryContract>).getSender(),
{
value: toNano('0.05'),
}
);
const str2 = await pingPongContract.getStr();
const str2Ok = (str2 as any).toString().replace('x{', '').replace('}', '');
const decoded2 = Buffer.from(str2Ok, 'hex').toString('utf8');
expect(decoded2).toBe('pong');

await pingPongContract.sendToggle(
(user1 as SandboxContract<TreasuryContract>).getSender(),
{
value: toNano('0.05'),
}
);
const str3 = await pingPongContract.getStr();
const str3Ok = (str3 as any).toString().replace('x{', '').replace('}', '');
const decoded3 = Buffer.from(str3Ok, 'hex').toString('utf8');
expect(decoded3).toBe('ping');
});
});
6 changes: 6 additions & 0 deletions wrappers/PingPong.compile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { CompilerConfig } from '@ton/blueprint';

export const compile: CompilerConfig = {
lang: 'func',
targets: ['contracts/pingpong.fc'],
};
60 changes: 60 additions & 0 deletions wrappers/PingPong.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {
Address,
beginCell,
Cell,
Contract,
contractAddress,
ContractProvider,
Sender,
SendMode,
} from '@ton/core';

export function pingPongConfigToCell(): Cell {
const forwardPayload = beginCell().storeStringTail('ping').endCell();
return beginCell().storeRef(forwardPayload).endCell();
}

export class PingPong implements Contract {
constructor(
readonly address: Address,
readonly init?: { code: Cell; data: Cell }
) {}

static createFromAddress(address: Address) {
return new PingPong(address);
}

static createFromConfig(config: object, code: Cell, workchain = 0) {
const data = pingPongConfigToCell();
const init = { code, data };
return new PingPong(contractAddress(workchain, init), init);
}

async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) {
const a = await provider.internal(via, {
value,
sendMode: SendMode.PAY_GAS_SEPARATELY,
body: beginCell().endCell(),
});
return a;
}

async sendToggle(
provider: ContractProvider,
via: Sender,
opts: {
value: bigint;
}
) {
await provider.internal(via, {
value: opts.value,
sendMode: SendMode.PAY_GAS_SEPARATELY,
body: beginCell().storeUint(10, 32).endCell(),
});
}

async getStr(provider: ContractProvider) {
const result = await provider.get('get_str', []);
return result.stack.readCell();
}
}

0 comments on commit f884239

Please sign in to comment.