As a static analyzer, TSA
can operate in two modes: runtime error detection for local smart contracts with report generation in SARIF format or test generation for Blueprint projects. For operating in this mode, use tsa-cli.jar
or corresponding options in the Docker Container.
Runtime Error Detection
In runtime error detection mode, TSA
accepts as input a contract file in one of the following formats: Tact (experimental) or FunC source code, or Fift assembler code, or BoC (compiled code). Optionally, it also accepts a TL-B scheme for the recv_internal
method (about TL-B schemes importance check the internal design-document). For detailed input format information, use the --help
argument.
The output in this mode is a SARIF report containing the following information about methods that may encounter a TVM error during execution:
- Instruction coverage percentage by the analyzer for the method (
coverage
field in the report) - Method number (
decoratedName
) and TVM bytecode instruction(stmt
) where the error may occur - Error code and type (
text
in amessage
) - Call stack
callFlows
(method id - instruction) - Possible (but not necessarily unique) parameters set
usedParameters
causing the error - Approximate gas usage
gasUsage
up to the error
For more information about error types, see the relevant section.
Examples
Consider a simple smart contract that may encounter a cell overflow error when the write
method receives a value greater than 4:
#include "stdlib.fc";
(builder) write(int loop_count) method_id {
builder b = begin_cell();
if (loop_count < 0) {
return b;
}
var i = 0;
repeat(loop_count) {
builder value = begin_cell().store_int(i, 32);
b = b.store_ref(value.end_cell());
}
return b;
}
() recv_internal(int msg_value, cell in_msg, slice in_msg_body) impure {
;; Do nothing
}
Running the analyzer for this contract with the following command (macOS ARM, assuming the contract, FunC and Fift stdlibs are located in the current directory):
docker run --platform linux/amd64 -it --rm -v $PWD:/project ghcr.io/espritoxyz/tsa:latest func -i /project/example.fc --func-std /project/stdlib.fc --fift-std /project/fiftstdlib
(please note that FunC stdlib is pointed using the specific option func-stdlib
, not as a part of the input file) identifies the error in the SARIF:
{
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
"version": "2.1.0",
"runs": [
{
"properties": {
"coverage": {
"0": 100.0,
"75819": 100.0
}
},
"results": [
{
"codeFlows": [
{
"threadFlows": [
{
"locations": [
{
"location": {
"logicalLocations": [
{
"decoratedName": "75819",
"properties": {
"stmt": "REPEAT#8"
}
}
]
}
}
]
}
]
}
],
"level": "error",
"locations": [
{
"logicalLocations": [
{
"decoratedName": "75819"
}
]
}
],
"message": {
"text": "TvmFailure(exit=TVM integer out of expected range, exit code: 5, type=UnknownError)"
},
"properties": {
"gasUsage": 220,
"usedParameters": [
"2147483648"
],
"resultStack": [
"0"
]
},
"ruleId": "integer-out-of-range"
}
],
"tool": {
"driver": {
"name": "TSA",
"organization": "Explyt"
}
}
}
]
}
Here the analyzed method has the id 75819
, the analyzer covered 100% instructions of this method, the integer out of expected range
error with exit code 5
occurred in the stmt 8
in the REPEAT
loop inside this method, 2147483648
value passed to this method causes this error, and gas usage before raising the error equals to 220
.
For more examples containing erroneous places, take a look at the directory in the repository with manually written contracts. Feel free to run TSA by yourself for these contracts or consider tests for them.
Test Generation
In test generation mode, TSA
takes as input a project in the Blueprint format and the relative path to the source code of the analyzed contract (as before, use --help
argument for more detailed information about input format).
In this mode, TSA
generates a corresponding wrapper
in Typescript
under the wrappers
directory and a test file for the contract in the tests
directory. The test file contains regression tests for execution branches of methods that terminate with a TVM error (for more information about error types, see the relevant section).
Examples
For the wallet-v4 contract, a test file will be generated with tests similar to the following:
import {Blockchain} from '@ton/sandbox'
import {Address, beginCell, Builder, Cell, Dictionary, DictionaryValue, Slice} from '@ton/core'
import '@ton/test-utils'
import {compileFunc} from "@ton-community/func-js"
import * as fs from "node:fs"
import {WalletV4Code} from "../wrappers/WalletV4Code"
async function compileContract(): Promise<Cell> {
let compileResult = await compileFunc({
targets: ['contracts/wallet-v4-code.fc'],
sources: (x) => fs.readFileSync(x).toString("utf8"),
})
if (compileResult.status === "error") {
console.error("Compilation Error!")
console.error(`\n${compileResult.message}`)
process.exit(1)
}
return Cell.fromBoc(Buffer.from(compileResult.codeBoc, "base64"))[0]
}
const sliceValue: DictionaryValue<Slice> = {
serialize: (src: Slice, builder: Builder) => {
builder.storeSlice(src)
},
parse: (src: Slice) => {
return src.clone();
}
}
describe('TvmTest', () => {
let code: Cell
let blockchain: Blockchain
beforeAll(async () => {
code = await compileContract()
})
beforeEach(async () => {
blockchain = await Blockchain.create()
})
it('test-0', async () => {
const data = beginCell().storeUint(BigInt("0b0"), 173).endCell()
const msgBody = beginCell().endCell()
const from = Address.parseRaw("0:0000000000000000000000000000000000000000000000000000000000000000")
const bounce = false
const bounced = false
const contractAddress = Address.parseRaw("0:0000000000000000000000000000000000000000000000000000000000000000")
const contract = blockchain.openContract(new WalletV4Code(contractAddress, { code, data }))
await contract.initializeContract(blockchain, 10000000n)
const sentMessageResult = await contract.internal(
blockchain,
from,
msgBody,
10000000n,
bounce,
bounced
)
expect(sentMessageResult.transactions).toHaveTransaction({
from: from,
to: contractAddress,
exitCode: 9,
})
})
})