Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed #7 issue #8

Merged
merged 5 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 140 additions & 17 deletions sources/contract.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
SandboxContract,
TreasuryContract,
internal,
printTransactionFees,
} from '@ton/sandbox';


Expand All @@ -13,13 +12,12 @@ import {
JettonMinter,
Mint,
TokenUpdateContent,
Deploy,
TokenBurn, ProvideWalletAddress
} from './output/Jetton_JettonMinter';
TokenBurn, ProvideWalletAddress, storeTokenTransfer, storeTokenBurn, storeMint
} from "./output/Jetton_JettonMinter";
import { JettonWallet, TokenTransfer } from "./output/Jetton_JettonWallet";

import "@ton/test-utils";
import { randomAddress } from './utils/utils';
import { getRandomInt, randomAddress } from "./utils/utils";

function jettonContentToCell(content: {type: 0|1, uri:string}) {
return beginCell()
Expand Down Expand Up @@ -259,11 +257,13 @@ describe("JettonMinter", () => {
to: deployerJettonWallet.address,
deploy: true,
});
//TODO Why to send excesses to jetton Minter? does this make sense?
// expect(mintResult.transactions).toHaveTransaction({ // excesses
// from: deployerJettonWallet.address,
// to: jettonMinter.address
// });
//Here was the check, that excesses are send to JettonMinter.
//This is an implementation-defined behavior
//In my implementation, excesses are sent to the deployer
expect(mintResult.transactions).toHaveTransaction({ // excesses
from: deployerJettonWallet.address,
to: deployer.address
});


expect(await deployerJettonWallet.getJettonBalance()).toEqual(initialJettonBalance);
Expand Down Expand Up @@ -413,7 +413,7 @@ describe("JettonMinter", () => {
let sentAmount = toNano('0.5');
let forwardAmount = toNano('0.05');
let forwardPayload = beginCell().storeUint(0x1234567890abcdefn, 128).endCell();
//TODO This block checks forward_payload in place (Either bit equals 0)
//This block checks forward_payload in place (Either bit equals 0)
const sendResult = await deployerJettonWallet.sendTransfer(deployer.getSender(), toNano('0.1'), //tons
sentAmount, notDeployer.address,
deployer.address, null, forwardAmount, forwardPayload);
Expand Down Expand Up @@ -443,15 +443,15 @@ describe("JettonMinter", () => {
});


//TODO There was no such test in official implementation
//There was no such test in official implementation
it('correctly sends forward_payload in ref', async () => {
const deployerJettonWallet = await userWallet(deployer.address);
let initialJettonBalance = await deployerJettonWallet.getJettonBalance();
const notDeployerJettonWallet = await userWallet(notDeployer.address);
let initialJettonBalance2 = await notDeployerJettonWallet.getJettonBalance();
let sentAmount = toNano('0.5');
let forwardAmount = toNano('0.05');
//TODO This block checks forward_payload in separate ref (Either bit equals 1)
//This block checks forward_payload in separate ref (Either bit equals 1)
let forwardPayload = beginCell().storeUint(1, 1).storeRef(beginCell().storeUint(0x1234567890abcdefn, 128).endCell()).endCell();

const sendResult = await deployerJettonWallet.sendTransfer(deployer.getSender(), toNano('0.1'), //tons
Expand Down Expand Up @@ -532,7 +532,129 @@ describe("JettonMinter", () => {

expect(await deployerJettonWallet.getJettonBalance()).toEqual(initialJettonBalance);
});
describe('Bounces', () => {
// This code is borrowed from the stablecoin implementation.
// The behavior is implementation-defined.
// I'm still not sure if the code handling these bounces is really necessary,
// but I could be wrong. Refer to this issue for details: https://github.com/tact-lang/jetton/issues/10
it('minter should restore supply on internal_transfer bounce', async () => {
const deployerJettonWallet = await userWallet(deployer.address);
const mintAmount = BigInt(getRandomInt(1000, 2000));
const mintMsg = beginCell().store(storeMint({$$type: "Mint", amount: mintAmount, receiver: deployer.address})).endCell();

const supplyBefore = await jettonMinter.getTotalSupply();
const minterSmc = await blockchain.getContract(jettonMinter.address);

// Sending message but only processing first step of tx chain
let res = await minterSmc.receiveMessage(internal({
from: deployer.address,
to: jettonMinter.address,
body: mintMsg,
value: toNano('1')
}));

expect(res.outMessagesCount).toEqual(1);
const outMsgSc = res.outMessages.get(0)!.body.beginParse();
expect(outMsgSc.preloadUint(32)).toEqual(Op.internal_transfer);

expect(await jettonMinter.getTotalSupply()).toEqual(supplyBefore + mintAmount);

await minterSmc.receiveMessage(internal({
from: deployerJettonWallet.address,
to: jettonMinter.address,
bounced: true,
body: beginCell().storeUint(0xFFFFFFFF, 32).storeSlice(outMsgSc).endCell(),
value: toNano('0.95')
}));

// Supply should change back
expect(await jettonMinter.getTotalSupply()).toEqual(supplyBefore);
});
it('wallet should restore balance on internal_transfer bounce', async () => {
const initRes = await jettonMinter.sendMint(deployer.getSender(), deployer.address, 201n, 0n, toNano(1));
const deployerJettonWallet = await userWallet(deployer.address);
expect(initRes.transactions).toHaveTransaction({
from: jettonMinter.address,
to: deployerJettonWallet.address,
success: true
})

const notDeployerJettonWallet = await userWallet(notDeployer.address);
const balanceBefore = await deployerJettonWallet.getJettonBalance();
const txAmount = BigInt(getRandomInt(100, 200));
const transferMsg = beginCell().store(storeTokenTransfer({$$type: "TokenTransfer",
query_id: 0n,
amount: txAmount,
response_destination: deployer.address,
destination: notDeployer.address,
custom_payload: null,
forward_ton_amount: 0n,
forward_payload: beginCell().endCell().beginParse()
})).endCell()

const walletSmc = await blockchain.getContract(deployerJettonWallet.address);

const res = await walletSmc.receiveMessage(internal({
from: deployer.address,
to: deployerJettonWallet.address,
body: transferMsg,
value: toNano('1')
}));
expect(res.outMessagesCount).toEqual(1);

const outMsgSc = res.outMessages.get(0)!.body.beginParse();
expect(outMsgSc.preloadUint(32)).toEqual(Op.internal_transfer);

expect(await deployerJettonWallet.getJettonBalance()).toEqual(balanceBefore - txAmount);

await walletSmc.receiveMessage(internal({
from: notDeployerJettonWallet.address,
to: walletSmc.address,
bounced: true,
body: beginCell().storeUint(0xFFFFFFFF, 32).storeSlice(outMsgSc).endCell(),
value: toNano('0.95')
}));

// Balance should roll back
expect(await deployerJettonWallet.getJettonBalance()).toEqual(balanceBefore);
});
it('wallet should restore balance on burn_notification bounce', async () => {
// Mint some jettons
await jettonMinter.sendMint(deployer.getSender(), deployer.address, 201n, 0n, toNano(1));
const deployerJettonWallet = await userWallet(deployer.address);
const balanceBefore = await deployerJettonWallet.getJettonBalance();
const burnAmount = BigInt(getRandomInt(100, 200));

const burnMsg = beginCell().store(storeTokenBurn({amount: burnAmount, $$type: "TokenBurn", query_id: 0n, response_destination: deployer.address, custom_payload: null})).endCell()

const walletSmc = await blockchain.getContract(deployerJettonWallet.address);

const res = await walletSmc.receiveMessage(internal({
from: deployer.address,
to: deployerJettonWallet.address,
body: burnMsg,
value: toNano('1')
}));

expect(res.outMessagesCount).toEqual(1);

const outMsgSc = res.outMessages.get(0)!.body.beginParse();
expect(outMsgSc.preloadUint(32)).toEqual(Op.burn_notification);

expect(await deployerJettonWallet.getJettonBalance()).toEqual(balanceBefore - burnAmount);

await walletSmc.receiveMessage(internal({
from: jettonMinter.address,
to: walletSmc.address,
bounced: true,
body: beginCell().storeUint(0xFFFFFFFF, 32).storeSlice(outMsgSc).endCell(),
value: toNano('0.95')
}));

// Balance should roll back
expect(await deployerJettonWallet.getJettonBalance()).toEqual(balanceBefore);
});
});
// implementation detail
it('works with minimal ton amount', async () => {
const deployerJettonWallet = await userWallet(deployer.address);
Expand Down Expand Up @@ -689,7 +811,8 @@ describe("JettonMinter", () => {
//let minimalFee = toNano("0.006");
let L = toNano(0.00000001);
let R = toNano(0.1);
//TODO change false to true if you want to find minimal fee
//change false to true if you want to find minimal fee
//However, before doing it, remove gas-checks from the smart-contract code
//implementing binary search
while(R - L > 1 && false) {
let minimalFee = (L + R) / 2n;
Expand All @@ -714,8 +837,8 @@ describe("JettonMinter", () => {

const sendLow = await deployerJettonWallet.sendBurn(deployer.getSender(), minimalFee, // ton amount
burnAmount, deployer.address, null); // amount, response address, custom payload
//TODO Here was tests, that checks that there is enough ton to jetton wallet to send a message.
//However I check that it is enough ton to process a message from jetton wallet to jetton minter
//Here was tests, that checks that there is enough ton to jetton wallet to send a message.
//However, I check that it is enough ton to process a message from jetton wallet to jetton minter
expect(sendLow.transactions).not.toHaveTransaction({
from: deployerJettonWallet.address,
to: jettonMinter.address,
Expand Down Expand Up @@ -823,7 +946,7 @@ describe("JettonMinter", () => {
//const fwdFee = 1464012n;
//const minimalFee = fwdFee + 10000000n; // toNano('0.0061');

//TODO Added binary search to find minimal fee
//Added binary search to find minimal fee
let L = toNano(0.00000001);
let R = toNano(0.1);
//Binary search here does not affect on anything except time of test
Expand Down
3 changes: 3 additions & 0 deletions sources/jetton_minter.tact
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ contract JettonMinter with OwnableTransferable {
});
}

bounced(msg: bounced<TokenTransferInternal>){
self.totalSupply -= msg.amount;
}
inline fun takeWalletBody(targetJettonWallet: Address, includedAddress: Address?, query_id: Int): Cell {
let body: Builder = beginCell()
.storeUint(0xd1735400, 32)
Expand Down
2 changes: 1 addition & 1 deletion sources/jetton_wallet.tact
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ contract JettonWallet with Ownable {
}

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

bounced(msg: bounced<TokenBurnNotification>){
Expand Down
Loading