-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
193 additions
and
57 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |