diff --git a/contracts/main.fc b/contracts/main.fc index bbe70ee..40ceb53 100644 --- a/contracts/main.fc +++ b/contracts/main.fc @@ -21,7 +21,7 @@ ;; message binary format is defined as TL-B in companion .tlb file -() op_withdraw(int withdraw_amount, slice owner_address); +() op_withdraw(int withdraw_amount, slice owner_address) impure; () recv_internal(int msg_value, cell in_msg, slice in_msg_body) impure { ;; parse incoming internal message @@ -48,6 +48,7 @@ if (op == op::deposit()) { ;; empty since ton received (msg_value) is added automatically to contract balance + ;; ~dump msg_value; ;; an example of debug output, requires running contract in debug mode return (); } @@ -68,7 +69,7 @@ throw(error::unknown_op()); } -() op_withdraw(int withdraw_amount, slice owner_address) { +() op_withdraw(int withdraw_amount, slice owner_address) impure { var [balance, _] = get_balance(); throw_unless(error::insufficient_balance(), balance >= withdraw_amount); int return_value = min(withdraw_amount, balance - const::min_tons_for_storage()); diff --git a/contracts/main.ts b/contracts/main.ts index 4d63119..c1eac59 100644 --- a/contracts/main.ts +++ b/contracts/main.ts @@ -1,3 +1,4 @@ +import BN from "bn.js"; import { Cell, beginCell, Address } from "ton"; // encode contract storage according to save_data() contract method @@ -11,6 +12,14 @@ export function increment(): Cell { return beginCell().storeUint(0x37491f2f, 32).storeUint(0, 64).endCell(); } +export function deposit(): Cell { + return beginCell().storeUint(0x47d54391, 32).storeUint(0, 64).endCell(); +} + +export function withdraw(params: { withdrawAmount: BN }): Cell { + return beginCell().storeUint(0x41836980, 32).storeUint(0, 64).storeCoins(params.withdrawAmount).endCell(); +} + export function transferOwnership(params: { newOwnerAddress: Address }): Cell { return beginCell().storeUint(0x2da38aaf, 32).storeUint(0, 64).storeAddress(params.newOwnerAddress).endCell(); } diff --git a/test/deposit.spec.ts b/test/deposit.spec.ts new file mode 100644 index 0000000..b682c08 --- /dev/null +++ b/test/deposit.spec.ts @@ -0,0 +1,84 @@ +import chai, { expect } from "chai"; +import chaiBN from "chai-bn"; +import BN from "bn.js"; +chai.use(chaiBN(BN)); + +import * as fs from "fs"; +import { Cell, toNano } from "ton"; +import { SmartContract } from "ton-contract-executor"; +import * as main from "../contracts/main"; +import { internalMessage, randomAddress, setBalance } from "./helpers"; + +describe("Deposit and withdraw tests", () => { + let contract: SmartContract; + + beforeEach(async () => { + contract = await SmartContract.fromCell( + Cell.fromBoc(fs.readFileSync("build/main.cell"))[0], // code cell from build output + main.data({ + ownerAddress: randomAddress("owner"), + counter: 17, + }) + ); + }); + + it("should get balance", async () => { + setBalance(contract, toNano(37)); + const call = await contract.invokeGetMethod("balance", []); + expect(call.result[0]).to.be.bignumber.equal(toNano(37)); + }); + + it("should allow the owner to withdraw when balance is high", async () => { + setBalance(contract, toNano(37)); + const send = await contract.sendInternalMessage( + internalMessage({ + from: randomAddress("owner"), + body: main.withdraw({ withdrawAmount: toNano(20) }), + }) + ); + expect(send.type).to.equal("success"); + expect(send.actionList).to.have.lengthOf(1); + const resultMessage = (send.actionList[0] as any)?.message?.info; + expect(resultMessage?.dest?.equals(randomAddress("owner"))).to.equal(true); + expect(resultMessage?.value?.coins).to.be.bignumber.equal(toNano(20)); + }); + + it("should prevent others from withdrawing when balance is high", async () => { + setBalance(contract, toNano(37)); + const send = await contract.sendInternalMessage( + internalMessage({ + from: randomAddress("notowner"), + body: main.withdraw({ withdrawAmount: toNano(20) }), + }) + ); + expect(send.type).to.equal("failed"); + expect(send.exit_code).to.equal(102); // access_denied in contracts/imports/constants.fc + }); + + it("should prevent the owner to withdraw when balance is low", async () => { + setBalance(contract, toNano(10)); + const send = await contract.sendInternalMessage( + internalMessage({ + from: randomAddress("owner"), + body: main.withdraw({ withdrawAmount: toNano(20) }), + }) + ); + expect(send.type).to.equal("failed"); + expect(send.exit_code).to.equal(103); // insufficient_balance in contracts/imports/constants.fc + }); + + it("should leave enough balance for rent", async () => { + setBalance(contract, toNano(20)); + const send = await contract.sendInternalMessage( + internalMessage({ + from: randomAddress("owner"), + body: main.withdraw({ withdrawAmount: toNano(20) }), + }) + ); + expect(send.type).to.equal("success"); + expect(send.actionList).to.have.lengthOf(1); + const resultMessage = (send.actionList[0] as any)?.message?.info; + expect(resultMessage?.dest?.equals(randomAddress("owner"))).to.equal(true); + expect(resultMessage?.value?.coins).to.be.bignumber.equal(toNano(20).sub(toNano(0.01))); // min_tons_for_storage in contracts/imports/constants.fc + }); +}); diff --git a/test/helpers.ts b/test/helpers.ts index 6f58397..d57397f 100644 --- a/test/helpers.ts +++ b/test/helpers.ts @@ -1,5 +1,6 @@ import BN from "bn.js"; import { Address, Cell, CellMessage, InternalMessage, CommonMessageInfo } from "ton"; +import { SmartContract } from "ton-contract-executor"; import Prando from "prando"; export const zeroAddress = new Address(0, Buffer.alloc(32, 0)); @@ -23,3 +24,10 @@ export function internalMessage(params: { from?: Address; to?: Address; value?: body: new CommonMessageInfo({ body: message }), }); } + +// temp fix until ton-contract-executor remembers c7 value between calls +export function setBalance(contract: SmartContract, balance: BN) { + contract.setC7Config({ + balance: balance.toNumber(), + }); +}