Skip to content

Commit 08209ef

Browse files
[NONEVM-3135] Contract tests - Unit test Deployable (#411)
* test: deployable opcodes and init * test: init and send * test: authorization * fix: typo * fix: duplicated test * fix: rm unnecesary await * fix: typo * fix: marc fn async * fix: deployable coverage
1 parent 9b17df0 commit 08209ef

File tree

6 files changed

+216
-6
lines changed

6 files changed

+216
-6
lines changed

contracts/contracts/deployable/types.tolk

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ struct (0xba466447) Deployable_Initialize {
2121
stateInit: ContractState;
2222
}
2323
// This message initializes the Deployable contract and immediately sends a message to itself to execute some logic. It allows to carry the message value.
24-
struct (0xe95e1156) Deployable_InitializeAndSend {
24+
struct (0xb0ec5157) Deployable_InitializeAndSend {
2525
stateInit: ContractState;
2626
selfMessage: Deployable_Message;
2727
}

contracts/tests/coverage/coverage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const CoverageContractName = {
1111
merkleroot: 'merkleroot',
1212
send_executor: 'send_executor',
1313
receive_executor: 'receive_executor',
14+
deployable: 'deployable',
1415
} as const
1516
export type CoverageConfigNames = keyof typeof CoverageContractName
1617

contracts/tests/coverage/scriptMergeCoverageIntoHtmlReports.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Coverage } from '@ton/sandbox'
22
import { readFileSync, readdirSync, writeFileSync } from 'fs'
33
import { join } from 'path'
4-
import * as coverage from './Coverage'
4+
import * as coverage from './coverage'
55

66
const offRampSuffix = `${coverage.CoverageContractName.offramp}.json`
77
const routerSuffix = `${coverage.CoverageContractName.router}.json`
@@ -10,6 +10,7 @@ const merkleRootSuffix = `${coverage.CoverageContractName.merkleroot}.json`
1010
const onRampSuffix = `${coverage.CoverageContractName.onramp}.json`
1111
const sendExecutorSuffix = `${coverage.CoverageContractName.send_executor}.json`
1212
const receiveExecutorSuffix = `${coverage.CoverageContractName.receive_executor}.json`
13+
const deployableSuffix = `${coverage.CoverageContractName.deployable}.json`
1314

1415
const offRampCoverageResults: Coverage[] = []
1516
const routerCoverageResults: Coverage[] = []
@@ -18,6 +19,7 @@ const merkleRootCoverageResults: Coverage[] = []
1819
const onrampCoverageResults: Coverage[] = []
1920
const sendExecutorCoverageResults: Coverage[] = []
2021
const receiveExecutorCoverageResults: Coverage[] = []
22+
const deployableCoverageResults: Coverage[] = []
2123

2224
const coverageDir = './.coverage'
2325

@@ -48,6 +50,9 @@ for (const file of files) {
4850
} else if (file.endsWith(receiveExecutorSuffix)) {
4951
const coverage = Coverage.fromJson(readFileSync(filePath, 'utf-8'))
5052
receiveExecutorCoverageResults.push(coverage)
53+
} else if (file.endsWith(deployableSuffix)) {
54+
const coverage = Coverage.fromJson(readFileSync(filePath, 'utf-8'))
55+
deployableCoverageResults.push(coverage)
5156
}
5257
}
5358

@@ -64,6 +69,7 @@ const merkleRootMerged = mergeResults(merkleRootCoverageResults)
6469
const onRampMerged = mergeResults(onrampCoverageResults)
6570
const sendExecutorMerged = mergeResults(sendExecutorCoverageResults)
6671
const receiveExecutorMerged = mergeResults(receiveExecutorCoverageResults)
72+
const deployableMerged = mergeResults(deployableCoverageResults)
6773

6874
// Generate HTML reports
6975
if (offRampMerged) {
@@ -121,3 +127,11 @@ if (receiveExecutorMerged) {
121127
)
122128
console.log('Generated receiveexecutor-coverage.html')
123129
}
130+
131+
if (deployableMerged) {
132+
writeFileSync(
133+
`./.coverage/${coverage.CoverageContractName.deployable}.html`,
134+
deployableMerged.report('html'),
135+
)
136+
console.log('Generated deployable-coverage.html')
137+
}
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import { beginCell, Cell, toNano } from '@ton/core'
2+
import { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox'
3+
import { crc32 } from 'zlib'
4+
5+
import * as coverage from '../../coverage/coverage'
6+
import { generateRandomContractId } from '../../../src/utils'
7+
8+
import * as counter from '../../../wrappers/examples/Counter'
9+
import * as dep from '../../../wrappers/libraries/Deployable'
10+
11+
describe('Deployable - Opcodes', () => {
12+
it('should match opcodes', () => {
13+
expect(dep.Opcodes.initialize).toBe(crc32('Deployable_Initialize'))
14+
expect(dep.Opcodes.initializeAndSend).toBe(crc32('Deployable_InitializeAndSend'))
15+
})
16+
})
17+
18+
describe('Deployable - Unit Tests', () => {
19+
let blockchain: Blockchain
20+
let deployer: SandboxContract<TreasuryContract>
21+
let deployableCode: Cell
22+
let counterCode: Cell
23+
let deployable: SandboxContract<dep.ContractClient>
24+
25+
beforeAll(async () => {
26+
deployableCode = await dep.ContractClient.code()
27+
counterCode = await counter.ContractClient.code()
28+
29+
blockchain = await Blockchain.create()
30+
blockchain.verbosity.debugLogs = true
31+
32+
if (process.env['COVERAGE'] === 'true') {
33+
blockchain.enableCoverage()
34+
blockchain.verbosity.print = false
35+
blockchain.verbosity.vmLogs = 'vm_logs_verbose'
36+
}
37+
})
38+
39+
beforeEach(async () => {
40+
deployer = await blockchain.treasury('deployer')
41+
42+
const data: dep.DeployableStorage = {
43+
owner: deployer.address,
44+
id: beginCell().storeStringTail('DeployableTests').storeUint(generateRandomContractId(), 32),
45+
}
46+
47+
deployable = blockchain.openContract(dep.ContractClient.createFromConfig(data, deployableCode))
48+
})
49+
50+
it('should initialize and replace code and data', async () => {
51+
const data = counter.builder.data.contractData
52+
.encode({
53+
id: Number(generateRandomContractId()),
54+
value: 0,
55+
ownable: {
56+
owner: deployer.address,
57+
pendingOwner: undefined,
58+
},
59+
})
60+
.asCell()
61+
const result = await deployable.sendInitialize(deployer.getSender(), toNano('0.05'), {
62+
stateInit: {
63+
code: counterCode,
64+
data,
65+
},
66+
})
67+
expect(result.transactions).toHaveTransaction({
68+
to: deployable.address,
69+
deploy: true,
70+
success: true,
71+
})
72+
73+
const counterContract = blockchain.openContract(
74+
counter.ContractClient.newAt(deployable.address),
75+
)
76+
expect(await counterContract.getValue()).toBe(0)
77+
78+
const resultIncrease = await counterContract.sendSetCount(
79+
deployer.getSender(),
80+
toNano('0.01'),
81+
{
82+
queryId: 1n,
83+
newCount: 42,
84+
},
85+
)
86+
expect(resultIncrease.transactions).toHaveTransaction({
87+
to: counterContract.address,
88+
success: true,
89+
})
90+
expect(await counterContract.getValue()).toBe(42)
91+
})
92+
93+
it('should initialize and send a message to self', async () => {
94+
const data = counter.builder.data.contractData
95+
.encode({
96+
id: Number(generateRandomContractId()),
97+
value: 0,
98+
ownable: {
99+
owner: deployable.address,
100+
pendingOwner: undefined,
101+
},
102+
})
103+
.asCell()
104+
const result = await deployable.sendInitializeAndSend(deployer.getSender(), toNano('0.05'), {
105+
stateInit: {
106+
code: await counter.ContractClient.code(),
107+
data,
108+
},
109+
selfMessage: {
110+
value: toNano('0.02'),
111+
body: counter.builder.message.in.setCount
112+
.encode({
113+
queryId: 1n,
114+
newCount: 42,
115+
})
116+
.asCell(),
117+
},
118+
})
119+
expect(result.transactions).toHaveTransaction({
120+
to: deployable.address,
121+
deploy: true,
122+
success: true,
123+
})
124+
expect(result.transactions).toHaveTransaction({
125+
from: deployable.address,
126+
to: deployable.address,
127+
success: true,
128+
op: counter.opcodes.in.SetCount,
129+
})
130+
131+
const counterContract = blockchain.openContract(
132+
counter.ContractClient.newAt(deployable.address),
133+
)
134+
expect(await counterContract.getValue()).toBe(42)
135+
})
136+
137+
it('should not allow non-owner to initialize', async () => {
138+
const other = await blockchain.treasury('other')
139+
140+
const result = await deployable.sendInitialize(other.getSender(), toNano('0.05'), {
141+
stateInit: {
142+
code: Cell.EMPTY,
143+
data: Cell.EMPTY,
144+
},
145+
})
146+
expect(result.transactions).toHaveTransaction({
147+
to: deployable.address,
148+
success: false,
149+
exitCode: dep.Errors.ErrorNotOwner,
150+
})
151+
})
152+
153+
it('should not allow non-owner to initialize and send', async () => {
154+
const other = await blockchain.treasury('other')
155+
156+
const result = await deployable.sendInitializeAndSend(other.getSender(), toNano('0.05'), {
157+
stateInit: {
158+
code: Cell.EMPTY,
159+
data: Cell.EMPTY,
160+
},
161+
selfMessage: {
162+
value: 0n,
163+
body: Cell.EMPTY,
164+
},
165+
})
166+
expect(result.transactions).toHaveTransaction({
167+
to: deployable.address,
168+
success: false,
169+
exitCode: dep.Errors.ErrorNotOwner,
170+
})
171+
})
172+
173+
afterAll(async () => {
174+
if (process.env['COVERAGE'] === 'true') {
175+
await coverage.generateCoverageArtifacts(blockchain, 'deployable_unit_tests', [
176+
{
177+
code: deployableCode,
178+
name: 'deployable',
179+
},
180+
])
181+
}
182+
})
183+
})

contracts/wrappers/examples/Counter.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ import {
1010
SendMode,
1111
Slice,
1212
} from '@ton/core'
13-
import * as typeAndVersion from '../libraries/versioning/TypeAndVersion'
14-
import * as ownable2step from '../libraries/access/Ownable2Step'
13+
import { compile } from '@ton/blueprint'
14+
1515
import { CellCodec } from '../utils'
16+
import * as ownable2step from '../libraries/access/Ownable2Step'
17+
import * as typeAndVersion from '../libraries/versioning/TypeAndVersion'
1618

1719
/// @dev Message to set the counter value.
1820
export type SetCount = {
@@ -164,6 +166,10 @@ export class ContractClient implements Contract, typeAndVersion.Interface {
164166
})
165167
}
166168

169+
static async code(): Promise<Cell> {
170+
return await compile('examples.Counter')
171+
}
172+
167173
async getValue(provider: ContractProvider): Promise<number> {
168174
const result = await provider.get('value', [])
169175
return result.stack.readNumber()

contracts/wrappers/libraries/Deployable.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {
1010
SendMode,
1111
Slice,
1212
} from '@ton/core'
13+
import { compile } from '@ton/blueprint'
14+
1315
import { CellCodec } from '../utils'
1416

1517
export type DeployableStorage = {
@@ -113,11 +115,11 @@ export const builder = {
113115

114116
export abstract class Opcodes {
115117
static initialize = 0xba466447
116-
static initializeAndSend = 0xe95e1156
118+
static initializeAndSend = 0xb0ec5157
117119
}
118120

119121
export enum Errors {
120-
ErrorNotOwner = 0x1,
122+
ErrorNotOwner = 37400,
121123
}
122124

123125
export class ContractClient implements Contract {
@@ -136,6 +138,10 @@ export class ContractClient implements Contract {
136138
return new ContractClient(contractAddress(workchain, init), init)
137139
}
138140

141+
static async code(): Promise<Cell> {
142+
return compile('Deployable')
143+
}
144+
139145
async sendInitialize(provider: ContractProvider, via: Sender, value: bigint, msg: Initialize) {
140146
await provider.internal(via, {
141147
value: value,

0 commit comments

Comments
 (0)