Skip to content

Commit

Permalink
Started rewriting tests, migraged to tact-template
Browse files Browse the repository at this point in the history
  • Loading branch information
Shvandre committed Nov 11, 2024
1 parent 242db98 commit 258cab8
Show file tree
Hide file tree
Showing 22 changed files with 1,299 additions and 2,677 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ yarn deploy # To deploy contract

To deploy a contract, follow these steps:

1. Define the [`contract.tact`](./sources/contract.tact) file that will be used as entry point of your contract.
1. Define the [`contract.tact`](./sources/jetton_minter.tact) file that will be used as entry point of your contract.
2. Customize the [`contract.deploy.ts`](./sources/contract.deploy.ts) file based on your `contract.tact` to generate a deployment link. It is crucial to ensure the proper invocation of the `init()` function within the contract.

If you rename `contract.tact`, make sure to update [`tact.config.json`](./tact.config.json) correspondingly. Refer to the [Tact Documentation](https://docs.tact-lang.org/language/guides/config) for detailed information.
Expand Down
1 change: 0 additions & 1 deletion sources/contract.deploy.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as fs from "fs";
import * as path from "path";
import { Address, contractAddress } from "@ton/core";
import { SampleTactContract } from "./output/sample_SampleTactContract";
import { prepareTactDeployment } from "@tact-lang/deployer";

(async () => {
Expand Down
1 change: 0 additions & 1 deletion sources/contract.read.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Address, contractAddress} from "@ton/core";
import { TonClient4 } from "@ton/ton";
import { SampleTactContract } from "./output/sample_SampleTactContract";

(async () => {
const client = new TonClient4({
Expand Down
964 changes: 936 additions & 28 deletions sources/contract.spec.ts

Large diffs are not rendered by default.

40 changes: 0 additions & 40 deletions sources/contract.tact

This file was deleted.

131 changes: 131 additions & 0 deletions sources/jetton_minter.tact
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import "@stdlib/ownable";
import "./jetton_wallet";
import "./messages";

asm fun emptyAddress(): Address { b{00} PUSHSLICE }

struct JettonMasterState {
totalSupply: Int as coins;
mintable: Bool;
adminAddress: Address;
jettonContent: Cell;
jettonWalletCode: Cell;
}

contract JettonMinter with OwnableTransferable {
totalSupply: Int as coins;
mintable: Bool;
owner: Address;
jettonContent: Cell;
jettonWalletCode: Cell;

init(owner: Address, jettonContent: Cell) {
self.totalSupply = 0;
self.mintable = true;
self.owner = owner;
self.jettonContent = jettonContent;
self.jettonWalletCode = initOf JettonWallet(self.owner, myAddress()).code;
}

receive(msg: TokenBurnNotification) {
//Check that the message is from msg.sender's jetton_wallet
require(sender() == self.getJettonWalletByOwner(msg.sender), "Not wallet owner");

self.totalSupply -= msg.amount;
if (msg.response_destination != null) {
send(SendParameters{
to: msg.response_destination!!,
value: 0,
bounce: false,
mode: SendRemainingValue,
body: TokenExcesses{
query_id: msg.query_id
}.toCell()
});
}
}

receive(msg: TokenUpdateContent){
self.requireOwner(); // Allow changing content only by owner
self.jettonContent = msg.content; // Update content
}

// https://github.com/ton-blockchain/TEPs/blob/master/text/0089-jetton-wallet-discovery.md
receive(msg: ProvideWalletAddress) {
require(context().value >= ton("0.0061"), "Insufficient gas");
let includedAddress: Address? = null;
let workchain: Int = parseStdAddress(msg.owner_address.asSlice()).workchain;
//Note, that emptyAddress != null, it is different values.
//We do like that according to TEP above
let targetJettonWallet: Address = emptyAddress();

//Here was no such check in Howard's code
if(workchain == 0) {
//Only in this case (address is from basechain) we can calculate the address
targetJettonWallet = contractAddress(initOf JettonWallet(msg.owner_address, myAddress()));
}
if (msg.include_address) {
includedAddress = msg.owner_address;
}
send(SendParameters{
to: sender(),
value: 0,
mode: SendRemainingValue,
body: TakeWalletAddress{
query_id: msg.query_id,
wallet_address: targetJettonWallet,
owner_address: includedAddress
}.toCell()
});
}
receive(msg: Mint) {
self.requireOwner(); // Allow minting only by owner
require(self.mintable, "Not mintable");
//Maybe we should check that msg.value is enough to cover the gas fees
//Or, maybe we should do self.totalSupply -= msg.amount if bounced.
//But there is no any check in Howard's code and in official funC code,
self.totalSupply += msg.amount; // Update total supply

let winit: StateInit = self.getJettonWalletInit(msg.receiver);

send(SendParameters{
to: contractAddress(winit),
value: 0,
bounce: true,
mode: SendRemainingValue,
body: TokenTransferInternal{
query_id: 0,
amount: msg.amount,
from: myAddress(),
response_destination: self.owner, // Owner is minting, so send excess to owner
forward_ton_amount: 0,
forward_payload: emptySlice()
}.toCell(),
code: winit.code,
data: winit.data
});
}

fun getJettonWalletByOwner(jetton_wallet_owner: Address): Address {
let ctx: Context = context();
let jwInit: StateInit = self.getJettonWalletInit(jetton_wallet_owner);
return contractAddress(jwInit);
}

fun getJettonWalletInit(address: Address): StateInit {
return initOf JettonWallet(address, myAddress());
}

get fun get_jetton_data(): JettonMasterState {
return JettonMasterState {
totalSupply: self.totalSupply,
mintable: self.mintable,
adminAddress: self.owner,
jettonContent: self.jettonContent,
jettonWalletCode: self.jettonWalletCode
}
}
get fun get_wallet_address(ownerAddress: Address): Address {
return self.getJettonWalletByOwner(ownerAddress);
}
}
145 changes: 145 additions & 0 deletions sources/jetton_wallet.tact
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
asm fun myCode(): Cell { MYCODE }

@interface("org.ton.jetton.wallet")
contract JettonWallet with Ownable {
balance: Int as coins;
owner: Address;
master: Address;
const minTonsForStorage: Int = ton("0.019");
const gasConsumption: Int = ton("0.013");

init(owner: Address, master: Address) {
self.balance = 0;
self.owner = owner;
self.master = master;
}


receive(msg: TokenTransfer) {
self.requireOwner();

let final: Int = (((10 * 2 + 2 * self.gasConsumption) + self.minTonsForStorage) + msg.forward_ton_amount); // Gas checks, forward_ton = 0.152

//Context() returns Structure with info about incoming message
require(context().value > final, "Unsufficient amount of TON attached");

self.balance -= msg.amount;

//coins type is unsigned type, so
//self.balance is unsigned when storing and loading (msg.amount is unsigned too),
//however when doing calculations it's value may be negative, so the check is correct
require(self.balance >= 0, "Invalid balance");
let init: StateInit = initOf JettonWallet(msg.destination, self.master);

let wallet_address: Address = contractAddress(init);
send(SendParameters{
to: wallet_address,
value: 0,
mode: SendRemainingValue,
bounce: true,
body: TokenTransferInternal {
query_id: msg.query_id,
amount: msg.amount,
from: self.owner,
response_destination: msg.response_destination,
forward_ton_amount: msg.forward_ton_amount,
forward_payload: msg.forward_payload
}.toCell(),
code: init.code,
data: init.data
}
);
}

receive(msg: TokenTransferInternal) {
// This message should come only from master, or from other JettonWallet
if (sender() != self.master) {
let init: StateInit = initOf JettonWallet(msg.from, self.master);
require(contractAddress(init) == sender(), "Sender is not another JettonWallet or JettonMaster");
}
// Update balance
self.balance += msg.amount;

//Commented require() here because self.balance and msg.amount are coins, so they are unsigned
//require(self.balance >= 0, "Invalid balance");
// Get value for gas

let ctx: Context = context(); //Context of current message
let msgValue: Int = ctx.value;
let tonBalanceBeforeMsg = myBalance() - msgValue;
let storageFee = self.minTonsForStorage - min(tonBalanceBeforeMsg, self.minTonsForStorage);
msgValue -= (storageFee + self.gasConsumption);
let fwd_fee: Int = ctx.readForwardFee();


if (msg.forward_ton_amount > 0) {
msgValue = ((msgValue - msg.forward_ton_amount) - fwd_fee);
send(SendParameters{
to: self.owner,
value: msg.forward_ton_amount,
mode: SendPayGasSeparately,
bounce: false,
body: TokenNotification{ // 0x7362d09c -- Remind the new Owner
query_id: msg.query_id,
amount: msg.amount,
from: msg.from,
forward_payload: msg.forward_payload
}.toCell()
});
}
// 0xd53276db -- Cashback to the original Sender
if (msg.response_destination != null && msgValue > 0) {
send(SendParameters{
to: msg.response_destination!!,
value: msgValue,
mode: SendIgnoreErrors, // Jetton transfer is already succeeded, Here was PayGasSeparately in Howard's code
bounce: false,
body: TokenExcesses{
query_id: msg.query_id
}.toCell()
}
);
}
}

receive(msg: TokenBurn){
self.requireOwner();

let ctx: Context = context();
self.balance = (self.balance - msg.amount); // Update balance
require(self.balance >= 0, "Invalid balance after burn");
let fwd_fee: Int = ctx.readForwardFee(); // Gas checks
require(ctx.value > ((fwd_fee + 2 * self.gasConsumption) + self.minTonsForStorage), "Invalid value - Burn");
// Burn tokens
send(SendParameters{
to: self.master,
value: 0,
mode: SendRemainingValue,
bounce: true,
body: TokenBurnNotification{
query_id: msg.query_id,
amount: msg.amount,
sender: self.owner,
response_destination: msg.response_destination
}.toCell()
}
);
}

bounced(msg: bounced<TokenTransferInternal>){
self.balance = (self.balance + msg.amount);
}

bounced(msg: bounced<TokenBurnNotification>){
self.balance = (self.balance + msg.amount);
}

get fun get_wallet_data(): JettonWalletData {
return JettonWalletData{
balance: self.balance,
owner: self.owner,
master: self.master,
code: myCode() //may be raplaced by "initOf JettonDefaultWallet(self.owner, self.master).code"
};
}
}
Loading

0 comments on commit 258cab8

Please sign in to comment.