部署后的链上测试
一旦当我们的合约部署上链后,非常正常的流程就是继续测试链上合约的功能性函数是否运行正常了。我们还需要写一个脚本来专门对合约功能进行测试。
在我们run这些test之前,为了确保合约的确部署完毕,我们需要先去获取TON blockchain的当前状态。有很多工具可以做到类似的事情,这里以TON Access为例。
首先在scripts文件夹下创建onchaintest.ts文件。并且在package.json中添加running shortcut。
{ ... our previous package.json keys "scripts": { ... previous scripts keys "onchaintest": "ts-node ./scripts/onchaintest.ts" } }
我们需要合约地址来验证它是否部署完毕。这个地址我们将独立计算。
独立计算合约地址的代码如下:
import { Cell, contractAddress } from "@ton"; import { hex } from "../build/main.compiled.json"; async function onchainTestScript() { const codeCell = Cell.fromBoc(Buffer.from(hex, "hex"))[0]; const dataCell = new Cell(); const address = contractAddress(0, { code: codeCell, data: dataCell, }); } onchainTestScript();
安装@orbs-netwotk/ton-access库:
yarn add @orbs-network/ton-access --save
现在加入通过地址来核验合约在链状态的代码:
import { Cell, contractAddress } from "@ton/core"; import { hex } from "../build/main.compiled.json"; import { getHttpV4Endpoint } from "@orbs-network/ton-access"; async function onchainTestScript() { const codeCell = Cell.fromBoc(Buffer.from(hex, "hex"))[0]; const dataCell = new Cell(); const address = contractAddress({ workchain: 0, initialCode: codeCell, initialData: dataCell, }); const endpoint = await getHttpV4Endpoint({ network: "testnet", }); const client4 = new TonClient4({ endpoint }); const latestBlock = await client4.getLastBlock(); let status = await client4.getAccount(latestBlock.last.seqno, address); if (status.account.state.type !== "active") { console.log("Contract is not active"); return; } } onchainTestScript()
以上代码将
- 使用 getHttpV4Endpoint 函数连接到 Orbs 团队托管的 API 端点,并获取 v4 协议的客户端对象。
- 获取上一个区块的信息。
- 使用 v4 客户端的 getAccount 方法,向其提供序列号和我们的合约地址
在合同尚未部署的情况下是可以停止脚本的。
开始实际测试时需要创建一个简单的交易链接,向合约地址发送 1 ton。与部署过程中一样,显示该链接的二维码后用TONHUB钱包手机应用扫描。
let link = `https://tonhub.com/transfer/` + address.toString({ testOnly: true, }) + "?" + qs.stringify({ text: "Simple test transaction", amount: toNano(1).toString(10), }); qrcode.generate(link, { small: true }, (code) => { console.log(code); });
我们还必须从 @ton/core 库中导入 toNano 函数将ton转换成nanos。一个ton有 1 000 000 000 nanos。在ton中,Ton coins的数量总是以nano为单位。但为了便于使用,大多数情况下,都以字符串形式使用nano。
此外,注意使用的是地址对象的 toString 方法,并且提供的配置参数强调了地址是来自测试网的。
显示二维码后,脚本将立即开始调用合约上的 getter 方法,以接收最新的sender地址。同时将只在最新发件人地址发生变化时在控制台记录日志,以避免在控制台记录相同的结果。
获取最新发件人地址的逻辑代码,我们将分析其中发生的情况,然后将其集成到我们的脚本中:
let recent_sender_archive: Address; setInterval(async () => { const latestBlock = await client4.getLastBlock(); const { exitCode, result } = await client4.runMethod( latestBlock.last.seqno, address, "get_the_latest_sender" ); if (exitCode !== 0) { console.log("Running getter method failed"); return; } if (result[0].type !== "slice") { console.log("Unknown result type"); return; } let most_recent_sender = result[0].cell.beginParse().loadAddress(); if ( most_recent_sender && most_recent_sender.toString() !== recent_sender_archive?.toString() ) { console.log( "New recent sender found: " + most_recent_sender.toString({ testOnly: true }) ); recent_sender_archive = most_recent_sender; } }, 2000);
代码分别做了什么事情:
- 设置了该函数每 2 秒重复一次
- 每次执行检查时,使用 getLastBlock 获取最新区块的序列号
- 使用 runMethod 调用获取方法,再用 exitCode 和 result呈现感兴趣的参数。
- 如果调用不成功或接收到的数据不属于片段类型,确保脚本停止运行(TON 中的地址总是一个片段)
- 获取 get 方法的结果,即result[0]第一项(result是一个数组,因为在其他情况下,getter 方法返回的结果可能不止一个)。
- 获取结果中的一个单元格参数,打开它进行解析,然后从中读取一个地址
- 将地址的字符串版本与我们收到的前一个发件人的字符串版本进行比较。如果两者不相等,我们就在控制台中记录新的最新发件人地址。
将代码集成:
import { Address, Cell, contractAddress, toNano } from "@ton/core"; import { hex } from "../build/main.compiled.json"; import { getHttpV4Endpoint } from "@orbs-network/ton-access"; import { TonClient4 } from "@ton/ton"; import qs from "qs"; import qrcode from "qrcode-terminal"; async function onchainTestScript() { const codeCell = Cell.fromBoc(Buffer.from(hex, "hex"))[0]; const dataCell = new Cell(); const address = contractAddress(0, { code: codeCell, data: dataCell, }); const endpoint = await getHttpV4Endpoint({ network: "testnet", }); const client4 = new TonClient4({ endpoint }); const latestBlock = await client4.getLastBlock(); let status = await client4.getAccount(latestBlock.last.seqno, address); if (status.account.state.type !== "active") { console.log("Contract is not active"); return; } let link = `https://test.tonhub.com/transfer/` + address.toString({ testOnly: true, }) + "?" + qs.stringify({ text: "Simple test transaction", amount: toNano(1).toString(10), }); qrcode.generate(link, { small: true }, (code) => { console.log(code); }); let recent_sender_archive: Address; setInterval(async () => { const latestBlock = await client4.getLastBlock(); const { exitCode, result } = await client4.runMethod( latestBlock.last.seqno, address, "get_the_latest_sender" ); if (exitCode !== 0) { console.log("Running getter method failed"); return; } if (result[0].type !== "slice") { console.log("Unknown result type"); return; } let most_recent_sender = result[0].cell.beginParse().loadAddress(); if ( most_recent_sender && most_recent_sender.toString() !== recent_sender_archive?.toString() ) { console.log( "New recent sender found: " + most_recent_sender.toString({ testOnly: true }) ); recent_sender_archive = most_recent_sender; } }, 2000); } onchainTestScript();
现在再跑一遍程序:
yarn onchaintest
如果操作正确无误,将会在终端看到一个二维码和最新的发件人地址。这个地址应该等于你的 Tonhub 钱包地址,也就是用来部署合约的地址。
为确保脚本按预期运行可以在 Tonhub 上创建另一个新钱包,在其中装入 testnet TON 币,然后再次将其发送到我们的合约中。几秒钟后就会在控制台中看到更新,因为最近的发送者地址发生了变化。
部署在主网络
现在调整一下脚本,准备将合约部署到mainnet上,首先需要更新下所有涉及主/测网的功能:
const endpoint = await getHttpV4Endpoint({ network: "mainnet", });
现在更新一下在deploy.ts文件中的代码:
import { hex } from "../build/main.compiled.json"; import { beginCell, Cell, contractAddress, StateInit, storeStateInit, toNano, } from "@ton/ton"; import qs from "qs"; import qrcode from "qrcode-terminal"; async function deployScript() { console.log( "=================================================================" ); console.log("Deploy script is running, let's deploy our main.fc contract..."); const codeCell = Cell.fromBoc(Buffer.from(hex, "hex"))[0]; const dataCell = new Cell(); const stateInit: StateInit = { code: codeCell, data: dataCell, }; const stateInitBuilder = beginCell(); storeStateInit(stateInit)(stateInitBuilder); const stateInitCell = stateInitBuilder.endCell(); const address = contractAddress(0, { code: codeCell, data: dataCell, }); console.log( `The address of the contract is following: ${address.toString()}` ); console.log(`Please scan the QR code below to deploy the contract:`); let link = `https://tonhub.com/transfer/` + address.toString({ testOnly: false, }) + "?" + qs.stringify({ text: "Deploy contract", amount: toNano(1).toString(10), init: stateInitCell.toBoc({ idx: false }).toString("base64"), }); qrcode.generate(link, { small: true }, (code) => { console.log(code); }); } deployScript();
只需修改一处代码:在记录合同地址时,当我们在 testnet 上时设置 testOnly 标志。
用同样的方法更新 onchaintest.ts 脚本:
import { Address, Cell, contractAddress, toNano } from '@ton/core'; import { hex } from '../build/main.compiled.json'; import { getHttpV4Endpoint } from '@orbs-network/ton-access'; import { TonClient4 } from '@ton/ton'; import qs from 'qs'; import qrcode from 'qrcode-terminal'; async function onchainTestScript() { const codeCell = Cell.fromBoc(Buffer.from(hex, 'hex'))[0]; const dataCell = new Cell(); const address = contractAddress(0, { code: codeCell, data: dataCell, }); const endpoint = await getHttpV4Endpoint({ network: process.env.TESTNET ? 'testnet' : 'mainnet', }); const client4 = new TonClient4({ endpoint }); const latestBlock = await client4.getLastBlock(); let status = await client4.getAccount(latestBlock.last.seqno, address); if (status.account.state.type !== 'active') { console.log('Contract is not active'); return; } let link = `https://tonhub.com/transfer/` + address.toString({ testOnly: process.env.TESTNET ? true : false, }) + '?' + qs.stringify({ text: 'Simple test transaction', amount: toNano(1).toString(10), }); qrcode.generate(link, { small: true }, (code) => { console.log(code); }); let recent_sender_archive: Address; setInterval(async () => { const latestBlock = await client4.getLastBlock(); const { exitCode, result } = await client4.runMethod( latestBlock.last.seqno, address, 'get_the_latest_sender', ); if (exitCode !== 0) { console.log('Running getter method failed'); return; } if (result[0].type !== 'slice') { console.log('Unknown result type'); return; } let most_recent_sender = result[0].cell.beginParse().loadAddress(); if ( most_recent_sender && most_recent_sender.toString() !== recent_sender_archive?.toString() ) { console.log( 'New recent sender found: ' + most_recent_sender.toString({ testOnly: true }), ); recent_sender_archive = most_recent_sender; } }, 2000); } onchainTestScript();
对 onchaintest.ts 脚本中的进行类似修改:
- 当我们连接到客户端以读取区块链时。
- 当记录合约地址时,只有当我们在 testnet 上时才会设置 testOnly 标志。
现在,我们可以在 testnet 和 mainnet 上部署和运行链上测试了。