Generating Wrappers
Learn how to automatically generate wrappers for Tolk contracts
When working on integration tests, you'll often need wrappers to send messages and call get-methods. To spare you from writing these manually, Acton offers the wrapper command, which automatically generates wrappers for your contracts.
Generating a Wrapper
To generate a wrapper for a contract defined in your Acton.toml, use the wrapper command:
acton wrapper CounterThis will:
- Generate a wrapper file (e.g.,
wrappers/Counter.gen.tolk). - Optionally generate a test stub if you pass the
--testflag.
Bootstrapping Tests with --test
Often when you start testing a contract, you need the same setup boilerplate: imports, deploying the contract, and basic checks. The --test flag can generate a ready-to-run test file for you.
acton wrapper Counter --testThis generates tests/Counter.test.tolk with a setupTest() helper and an example test case:
import "wrappers/Counter.gen"
import "@acton/emulation/testing"
// ... other imports
/// Initializes the test environment, creating a fresh instance of the contract.
/// Returns the contract wrapper and two treasury accounts (`deployer` and `notDeployer`).
fun setupTest() {
// Create a treasury account for deployment (typically the owner)
val deployer = testing.treasury("deployer");
// Create another treasury account for testing interactions from other users
val notDeployer = testing.treasury("not_deployer");
// Initialize and deploy the contract with default values
val contract = Counter.fromStorage({
id: 0,
counter: 0,
});
val res = contract.deploy(deployer.address, { value: ton("1") });
expect(res).toHaveSuccessfulDeploy({ to: contract.address });
return (contract, deployer, notDeployer);
}
/// Example test case demonstrating the basic flow
get fun `test basic flow`() {
val (contract, deployer, notDeployer) = setupTest();
// TODO: Implement your test logic here
// Example:
// val res = contract.sendMsg(deployer.address, ...);
// expect(res).toHaveTransaction({ ... });
}This gets you from zero to a running test in one command!
Requirements and "Gotchas"
Contract Header Is Required
Wrapper generation uses the ABI emitted by the Tolk compiler. The contract header is the source of truth for:
- storage, via
storage: ... - incoming message wrappers, via
incomingMessages: ...
If incomingMessages is not declared in the contract header, send{MessageName} methods will not be generated. If storage is missing, the wrapper falls back to an empty fromStorage() initializer instead of a typed storage-based one.
contract Counter {
storage: Storage
incomingMessages: AllowedMessage
}
struct Storage {
counter: uint32
}
struct (0x7e8764ef) Increase {
amount: int32
}
type AllowedMessage = Increase
fun onInternalMessage(in: InMessage) {
val msg = lazy AllowedMessage.fromSlice(in.body);
match (msg) {
Increase => { ... }
}
}Types Can Be Local Or Imported
Storage and message types may live either in the contract file itself or in imported files. Both layouts are supported.
struct (0x7e8764ef) Increase {
amount: int32
}
type AllowedMessage = Increaseimport "types"
contract Counter {
storage: Storage
incomingMessages: AllowedMessage
}
struct Storage {
counter: uint32
}
fun onInternalMessage(in: InMessage) {
val msg = lazy AllowedMessage.fromSlice(in.body);
match (msg) {
Increase => { ... }
}
}Contract must be in Acton.toml
The command takes a contract name, not a file path. Ensure your contract is defined in the configuration:
[contracts.Counter]
display-name = "Counter"
src = "contracts/Counter.tolk"Using the Generated Wrapper
The generated wrapper provides a struct representing your contract and methods to interact with it.
Initializing and Deploying
The wrapper provides a static fromStorage method to create the initial state and a deploy method to put it on the chain.
import "@wrappers/Counter"
// Don't forget to import types for the storage struct!
import "@contracts/types"
import "@acton/emulation/testing"
get fun `test example`() {
// 1. Create contract instance from storage data
val contract = Counter.fromStorage({
counter: 0,
});
// 2. Deploy it
val deployer = testing.treasury("deployer");
contract.deploy(deployer.address, { value: ton("1") });
}Sending Messages
For every message type listed in contract { incomingMessages: ... }, the wrapper generates a corresponding send{MessageName} method.
If your contract handles Increase:
// Generated method signature:
fun Counter.sendIncrease(self, from: address, amount: int, config: ...): SendResultListYou can use it like this:
// Send the "Increase" message
contract.sendIncrease(deployer.address, 10);
// With custom value and bounce flag
contract.sendIncrease(deployer.address, 10, {
value: ton("0.5"),
bounce: true
});Calling Get Methods
Get-methods are also wrapped. They return typed values directly.
val current = contract.currentCounter();
expect(current).toEqual(10);Command Options
You can customize the output paths:
# Generate wrapper in a specific file
acton wrapper Counter --output tests/my_wrapper.tolk
# Generate wrapper into a specific directory
acton wrapper Counter --output-dir tests/generated
# Generate a test file alongside the wrapper
acton wrapper Counter --test --test-output tests/my.test.tolk
# Generate a test file into a specific directory
acton wrapper Counter --test --test-output-dir tests/generatedYou can also configure project-wide defaults in Acton.toml:
[wrappers.tolk]
output-dir = "tests/generated-wrappers"
generate-test = true
test-output-dir = "tests/generated-tests"
[wrappers.typescript]
output-dir = "./wrappers-ts"Then:
acton wrapper Counterwritestests/generated-wrappers/Counter.gen.tolkandtests/generated-tests/Counter.test.tolkacton wrapper Counter --tswriteswrappers-ts/Counter.gen.tsby default
See the wrapper command reference for more details about CLI options.
Last updated on