This project is not audited
If you want to use this project, perform your own verification or send an email to admin@cmta.ch.
The SnapshotEngine is a smart contract system designed to perform on-chain snapshots, making it easier to distribute dividends or other token-based rewards directly on-chain.
It is intended to work with any standard ERC-20 token (for example, CMTAT).
If you want to integrate it into another contract—such as one for distributing dividends—you can access balance and state information through the ISnapshotState interface, defined in ISnapshotState.sol.
The codebase is modular, allowing you to use or extend only the components you need. Thus, instead of using the SnapshotEngine as an external contract called by the ERC-20 token, you can integrate the relevant modules directly in the token smart contract. This repository provides examples with CMTAT (upgradeable and standalone variants), see CMTAT deployment version.
CMTAT/is a git submodule and should be treated as external code.- Local changes in this repository should not modify code inside the
CMTAT/submodule.
SnapshotEngine(contracts/deployment/SnapshotEngine.sol): external engine with AccessControlEnumerable (SNAPSHOOTER_ROLE).SnapshotEngineOwnable2Step(contracts/deployment/SnapshotEngineOwnable2Step.sol): external engine with Ownable2Step authorization.CMTATUpgradeableInternalSnapshot(contracts/deployment/CMTATUpgradeableInternalSnapshot.sol): internal snapshot logic integrated in a local CMTAT upgradeable deployment.CMTATStandaloneInternalSnapshot(contracts/deployment/CMTATStandaloneInternalSnapshot.sol): internal snapshot logic integrated in a local CMTAT standalone deployment.
Current deployed sizes of the main local deployment contracts:
SnapshotEngine:8.772 KiBSnapshotEngineOwnable2Step:7.387 KiBCMTATUpgradeableInternalSnapshot:23.963 KiBCMTATStandaloneInternalSnapshot:23.963 KiB
- Repository notes
- Deployment variants
- Quick start
- When to use it
- How to include it
- CMTAT deployment version
- Schema
- Technical
- Access Control
- Events
- Ethereum API
- Storage management (ERC-7201)
- Usage instructions
- Generate documentation
- Security
- Further reading
- Intellectual property
npm install
npx hardhat compile
npx hardhat testIncident recovery: prefer pinned balanceOf reads instead of the snapshot engine
In outages/reorgs, the simplest and most accurate recovery is eth_call to balanceOf(address) at a pinned, finalized block. That gives exact balances at the last healthy point even if new blocks aren’t produced.
In short:
-
Snapshots are best for on-chain logic at a record date (airdrops/dividends, voting, escrows).
-
Pinned balanceOf at a block is best for off-chain recovery, reporting, or building a Merkle list.
While it has been designed for the CMTAT, the SnapshotEngine can be used with other ERC-20 contracts to perform on-chain snapshots.
For SnapshotEngine deployment, the bound token only needs to expose:
balanceOf(address)totalSupply()
This minimal requirement is captured by IERC20SnapshotCompatible in contracts/interface/IERC20SnapshotCompatible.sol.
To use it, import in your contract the interface ISnapshotEngine which declares the function operateOnTransfer.
This interface can be found in CMTAT/contracts/interfaces/engine
/*
* @dev minimum interface to define a SnapshotEngine
*/
interface ISnapshotEngine {
/**
* @notice Records balance and total supply snapshots before any token transfer occurs.
* @dev This function should be called inside the {_update} hook so that
* snapshots are updated prior to any state changes from {_mint}, {_burn}, or {_transfer}.
* It ensures historical balances and total supply remain accurate for snapshot queries.
*
* @param from The address tokens are being transferred from (zero address if minting).
* @param to The address tokens are being transferred to (zero address if burning).
* @param balanceFrom The current balance of `from` before the transfer (used to update snapshot).
* @param balanceTo The current balance of `to` before the transfer (used to update snapshot).
* @param totalSupply The current total supply before the transfer (used to update snapshot).
*/
function operateOnTransfer(address from, address to, uint256 balanceFrom, uint256 balanceTo, uint256 totalSupply) external;
}During each ERC-20 transfer, before updating the balances and total supply, your contract must call the function operateOnTransfer which is the entrypoint for the SnapshotEngine.
For a CMTAT deployment that supports an external snapshot engine, deploy the CMTAT snapshot-enabled variant from the CMTAT repository, deploy SnapshotEngine or SnapshotEngineOwnable2Step with the token address, then call setSnapshotEngine on the token to bind the engine.
The local CMTATUpgradeableInternalSnapshot and CMTATStandaloneInternalSnapshot contracts in this repository are not used for that flow. They are internal-snapshot-only deployment variants.
| SnapshotEngine version | CMTAT Compatible Versions |
|---|---|
v0.5.0 (unaudited) |
v3.0.0, v3.1.0, v3.2.0 v.3.3.0 (CMTAT Snapshot + Debt) |
v0.4.0 (unaudited) |
v3.0.0, v3.1.0, v3.2.0 v.3.3.0 (CMTAT Snapshot + Debt) |
v0.3.0 (unaudited) |
v3.0.0, v3.1.0, v3.2.0 |
| ``v0.2.0`(unaudited) | v3.0.0-rc7 |
This repository also contains CMTAT deployment versions with the required snapshot modules integrated:
CMTATUpgradeableInternalSnapshotfor proxy deployment.
CMTATStandaloneInternalSnapshotfor non-proxy deployment (initialized through constructor).
- The CMTAT features are included by inheriting from the CMTAT base contract
CMTATBaseRuleEngineand overriding the internalupdatefunction (from OpenZeppelin’s ERC20) to call_snapshotUpdate. This internal function is responsible for updating balances and total supply whenever a snapshot is detected. - For each ERC-20 transfer, the
_updatefunction is called, and a snapshot is materialized when required. Since the snapshot logic is integrated directly into the token, there is no need for an externalSnapshotEnginecontract. - These local deployment variants intentionally do not include
CMTATBaseSnapshot/setSnapshotEnginesupport. That combination pushed the deployment bytecode above the EVM smart contract size limit, so the localCMTAT*Snapshotcontracts are now internal-snapshot-only variants.
If you need a CMTAT token that is wired to an external snapshot engine through setSnapshotEngine, use the snapshot-enabled deployment variants provided in the CMTAT repository instead of the local CMTATUpgradeableInternalSnapshot / CMTATStandaloneInternalSnapshot contracts from this repository.
The main contract is SnapshotEngine
SnapshotEngine is a thin deployment wrapper. SnapshotEngineBase provides the shared snapshot logic (state reads, scheduling, update hooks, versioning, and bound-token checks), AccessControlEnumerable provides role-based authorization, and SnapshotEngine itself wires the two together by defining SNAPSHOOTER_ROLE, granting the initial admin role, and implementing _authorizeSnapshot().
The alternative ownable deployment contract is SnapshotEngineOwnable2Step
SnapshotEngineOwnable2Step is also a thin deployment wrapper. SnapshotEngineBase provides the shared snapshot logic (state reads, scheduling, update hooks, versioning, and bound-token checks), Ownable2Step provides two-step ownership management, and SnapshotEngineOwnable2Step wires the two together by setting the initial owner in the constructor and implementing _authorizeSnapshot() with onlyOwner.
As recommended by ABDK for the audit on CMTAT v1.0.0 (2021), we use an ordered array of scheduled snapshots and we don't remove already created (past) snapshots.
We recommend using an ordered array of scheduled snapshots and don’t remove already created snapshots from it, so the snapshot ID is the index in this array.
When snapshot is scheduled, its time should be greater that the currency block timestamp and shouldn’t be less than time of the latest scheduled snapshot (if any).
When snapshot is rescheduled, its new scheduled time shouldn’t be less than the time of the previous scheduled snapshot (if any) and shouldn’t be greater than the time of the next scheduled snapshot (if any).
Only the latest scheduled snapshot could be unscheduled.
Such approach would make it possible to use binary search to find the current snapshot index. It also would make the snapshot ID known when snapshot is just scheduled, and would make it possible to know on- chain the scheduled times of already created snapshots. It would also allow scheduling several snapshots at the same time (note: we don't allow that) and usint snapshot IDs instead of times to identify the scheduled snapshots.
Initially, we use an unordered list of snapshots, but this has a lot of disadvantage as pointed by ABDK
Using an unordered list of scheduled snapshots and removing already created snapshots from it is suboptimal and have several important drawbacks:
- The ID of ascheduled snapshot is unknown before the snapshot is currently created. This limits possibilities of scheduling snapshots from smart contracts.
- Schedule times for already created snapshots are not available on-chain.
- Each transfer requires to read the entire snapshot array, which is a significant overhead
Warning If a large backlog of scheduled snapshots becomes overdue before any transfer or
poke()call materializes them,_setCurrentSnapshot()may need to scan too many past timestamps in a single transaction. In that case, token transfers, mints, burns, orpoke()can run out of gas and revert until the backlog is handled. Avoid scheduling an excessively dense backlog of timestamps, and materialize snapshots regularly.
| Name | Function | Description | Implemented [yes, no] | Complexity | Best case | Worst case |
|---|---|---|---|---|---|---|
| Schedule snasphot in the future, after all current snapshots | scheduleSnapshot |
- | ✔ | O(1) | ||
| Schedule a snapshot at a random place in the future | scheduleSnapshotNotOptimized |
- | ✔ | O(N) | O(1) | O(N) |
| Schedule snasphot in the past | - | - | ✘ | O(N) | O(1) | O(N) |
| Reschedule a snapshot (in the future) | _rescheduleSnapshot |
The new time is in the range between the previous snapshot and the next snapshot | ✔ | O(1) | ||
| Reschedule a snapshot (in the future) | - | The new time can be after or before another existent snapshot | ✘ | O(N) | O(1) | O(N) |
| Reschedule a snapshot (in the past) | - | The new time can be in the past | ✘ | - | ||
| Unschedule the last snapshot | _unscheduleSnapshot |
- | ✔ | O(1) | ||
| Unschedule a random snapshot in the past | _unscheduleNotOptimized |
- | ✔ | O(N) | O(1) | O(N) |
| Unschedule a random snapshot in the future | _unscheduleNotOptimized |
- | ✔ | O(N) | O(1) | O(N) |
| Set the current snapshot | _setCurrentSnapshot |
- | ✔ | Same as _findScheduledMostRecentPastSnapshot |
||
| Update snapshots of the balance of an account | _updateAccountSnapshot |
- | ✔ | Same as _updateSnapshot |
||
| Update snapshots of the total Supply | _updateTotalSupplySnapshot |
- | ✔ | Same as _updateSnapshot |
||
| Get the last snapshot time inside a snapshot ids array | _lastSnapshot |
- | ✔ | O(1) | ||
| Find a snapshot | _findScheduledSnapshotIndex |
Find the snapshot index at the specified time | ✔ | O(log2(N)) We use a binary search to find the value at the specified time |
||
| Find the mot recent past snapshot | _findScheduledMostRecentPastSnapshot |
- | ✔ | O(1) We only have a O(N) complexity (worst case) if all next scheduled snapshot are situated in the past but no update of the current snapshot has been made. |
O(1) | O(N) |
| Update balance and/or total supply snapshots before the values are modified | _update transferred |
Call before each transfer. It is very important to have a low complexity because this function is called very often. | ✔ | The complexity depends of th functions _setCurrentSnapshot _updateAccountSnapshot _updateTotalSupplySnapshot |
||
| Get the next scheduled snapshotd | getNextSnapshots |
- | ✔ | O(N) Nevertheless, we maintain a pointer on the actual snapshot to avoid loop through past snapshot |
||
| Get all snapshot | getAllSnapshots |
- | ✔ | O(1) We directly return the array |
||
| Get the balance of an tokenHolder st the time specified | snapshotBalanceOf |
Return the number of tokens owned by the given tokenHolder at the time when the snapshot with the given time was created. | ✔ | O(log2(N)) We use a binary search to find the value at the snapshot time |
||
| Get the total supply at the time specified | snapshotTotalSupply |
- | ✔ | O(log2(N)) We use a binary search to find the value at the snapshot time |
Here are several schema to explain the main functions
Shows how the engine computes and returns the list of future scheduled snapshot timestamps.
Shows how a new future snapshot timestamp is validated and inserted into the schedule.
Shows how an existing scheduled snapshot is moved to a new valid timestamp.
Shows how a scheduled snapshot is removed from the schedule.
Two authorization models are available depending on deployment:
SnapshotEngine: role-based access control viaSNAPSHOOTER_ROLE.SnapshotEngineOwnable2Step: owner-only access viaonlyOwner.
Integrated CMTAT snapshot deployments use CMTAT role-based access control for snapshot scheduling functions.
Here is the list of roles and their 32 bytes identifier.
| Defined in | 32 bytes identifier | |
|---|---|---|
| DEFAULT_ADMIN_ROLE | OpenZeppelin AccessControl |
0x0000000000000000000000000000000000000000000000000000000000000000 |
| SNAPSHOOTER_ROLE | SnapshotScheduler |
0x809a0fc49fc0600540f1d39e23454e1f6f215bc7505fa22b17c154616570ddef |
The ERC-20 token bound to SnapshotEngine is set at deployment and cannot be changed afterward.
Only the bound token contract can call operateOnTransfer in SnapshotEngine.
The snapshot-related events are defined once in ISnapshotBase and emitted by the shared SnapshotBase logic.
These external engine deployments emit:
SnapshotSchedule(oldTime, newTime): emitted when a snapshot is scheduled for the first time or rescheduled.SnapshotUnschedule(time): emitted when a scheduled snapshot is canceled.SnapshotMaterialized(time, blockNumber): emitted when a scheduled snapshot becomes the current materialized snapshot.
These integrated CMTAT deployments emit the same snapshot events through their inherited snapshot base logic:
SnapshotSchedule(oldTime, newTime)SnapshotUnschedule(time)SnapshotMaterialized(time, blockNumber)
- The same event names and parameters are used across the external-engine and internal-snapshot deployment variants.
- Additional ERC-20 / CMTAT events may also be emitted by the integrated token deployments through upstream CMTAT modules, but those are outside the SnapshotEngine-specific event set described here.
Base contract for snapshot engines, providing common errors and read-only functions to query snapshots.
SnapshotSchedule(uint256 indexed oldTime, uint256 indexed newTime)Emitted when a snapshot is scheduled for the first time or rescheduled.
Input Parameters:
| Name | Type | Description |
|---|---|---|
| oldTime | uint256 | The previous scheduled timestamp (0 if newly scheduled). |
| newTime | uint256 | The new scheduled timestamp for the snapshot. |
SnapshotUnschedule(uint256 indexed time)Emitted when a previously scheduled snapshot is canceled.
Input Parameters:
| Name | Type | Description |
|---|---|---|
| time | uint256 | The timestamp of the snapshot that was unscheduled. |
SnapshotEngine_SnapshotScheduledInThePast(uint256 time, uint256 timestamp)Thrown when attempting to schedule a snapshot at a time earlier than the current block timestamp.
Input Parameters:
| Name | Type | Description |
|---|---|---|
| time | uint256 | The snapshot time requested. |
| timestamp | uint256 | The current block timestamp. |
SnapshotEngine_SnapshotTimestampBeforeLastSnapshot(uint256 time, uint256 lastSnapshotTimestamp)Thrown when a snapshot timestamp is earlier than the last snapshot timestamp.
Input Parameters:
| Name | Type | Description |
|---|---|---|
| time | uint256 | The snapshot time requested. |
| lastSnapshotTimestamp | uint256 | The timestamp of the most recent snapshot. |
SnapshotEngine_SnapshotTimestampAfterNextSnapshot(uint256 time, uint256 nextSnapshotTimestamp)Thrown when a snapshot timestamp is later than the next scheduled snapshot timestamp.
Input Parameters:
| Name | Type | Description |
|---|---|---|
| time | uint256 | The snapshot time requested. |
| nextSnapshotTimestamp | uint256 | The timestamp of the next scheduled snapshot. |
SnapshotEngine_SnapshotTimestampBeforePreviousSnapshot(uint256 time, uint256 previousSnapshotTimestamp)Thrown when a snapshot timestamp is earlier than the previous snapshot timestamp.
Input Parameters:
| Name | Type | Description |
|---|---|---|
| time | uint256 | The snapshot time requested. |
| previousSnapshotTimestamp | uint256 | The timestamp of the previous snapshot. |
Thrown when attempting to schedule a snapshot that already exists.
Thrown when attempting to execute or schedule a snapshot that has already been taken.
Thrown when attempting to unschedule or interact with a snapshot when no snapshot is currently scheduled.
Thrown when querying or modifying a snapshot that cannot be found.
Get all snapshots that have been created.
Return Values:
| Name | Type | Description |
|---|---|---|
| snapshots | uint256[] | Array of timestamps of all existing snapshots. |
Get the next scheduled snapshots that have not yet been created.
Return Values:
| Name | Type | Description |
|---|---|---|
| nextSnapshots | uint256[] | Array of timestamps of all future scheduled snapshots. |
Abstract contract for scheduling, rescheduling, and canceling snapshots.
Provides methods to manage snapshot times (expressed in seconds since epoch) with role-based access control via SNAPSHOOTER_ROLE.
function poke() publicMaterializes the latest eligible scheduled snapshot (if any), without requiring an ERC-20 transfer.
Details:
- Useful when no transfer/mint/burn occurs around a scheduled record date.
- Access is restricted by deployment mode:
SNAPSHOOTER_ROLEforSnapshotEngine,onlyOwnerforSnapshotEngineOwnable2Step.
function scheduleSnapshot(uint256 time)
publicSchedules a snapshot at the given time (in seconds since epoch).
Details:
- The scheduled time cannot be before the latest scheduled but not yet created snapshot.
- Access is restricted to accounts with
SNAPSHOOTER_ROLE.
Input Parameters:
| Name | Type | Description |
|---|---|---|
| time | uint256 | The scheduled time of the snapshot. |
function scheduleSnapshotNotOptimized(uint256 time)
publicSchedules a snapshot at the given time (non-optimized version).
Details:
- The scheduled time cannot be before the latest scheduled but not yet created snapshot.
- Access is restricted to accounts with
SNAPSHOOTER_ROLE.
Input Parameters:
| Name | Type | Description |
|---|---|---|
| time | uint256 | The scheduled time of the snapshot. |
function rescheduleSnapshot(uint256 oldTime,uint256 newTime)
publicReschedules a snapshot from oldTime to newTime.
Details:
- The new time cannot be before the previous scheduled snapshot or after the next scheduled snapshot.
- Access is restricted to accounts with
SNAPSHOOTER_ROLE.
Input Parameters:
| Name | Type | Description |
|---|---|---|
| oldTime | uint256 | The original scheduled time of the snapshot. |
| newTime | uint256 | The new scheduled time of the snapshot. |
function unscheduleLastSnapshot(uint256 time)
publicCancels the creation of the last scheduled snapshot at the given time.
Details:
- There must not be any other snapshots scheduled after this one.
- Access is restricted to accounts with
SNAPSHOOTER_ROLE.
Input Parameters:
| Name | Type | Description |
|---|---|---|
| time | uint256 | The scheduled time of the snapshot to cancel. |
function unscheduleSnapshotNotOptimized(uint256 time)
publicCancels the creation of a scheduled snapshot at the given time (non-optimized version).
Details:
- Access is restricted to accounts with
SNAPSHOOTER_ROLE.
Input Parameters:
| Name | Type | Description |
|---|---|---|
| time | uint256 | The scheduled time of the snapshot to cancel. |
Minimal interface for contracts (e.g. SnapshotEngine or CMTAT) supporting historical balance and total supply queries using snapshots. Provides read-only methods to retrieve account balances and total token supply at specific timestamps, either individually or in batch.
function snapshotBalanceOf(uint256 time,address tokenHolder)
public view returns (uint256 tokenHolderBalance);Gets the balance of a specific account at the snapshot corresponding to a given timestamp.
Input Parameters:
| Name | Type | Description |
|---|---|---|
| time | uint256 | The timestamp identifying the snapshot to query. |
| tokenHolder | address | The address whose balance is being requested. |
Return Values:
| Name | Type | Description |
|---|---|---|
| balance | uint256 | The recorded balance at the snapshot, or the current balance if no snapshot exists for that timestamp. |
function snapshotBalanceOfExact(uint256 time, address tokenHolder)
public view returns (uint256 tokenHolderBalance);Gets the balance at an exact scheduled snapshot timestamp.
Details:
- Reverts with
SnapshotEngine_SnapshotNotFoundiftimeis not an exact scheduled snapshot.
function snapshotTotalSupply(uint256 time)
public view returns (uint256 totalSupply)Gets the total token supply at the snapshot corresponding to a given timestamp.
Input Parameters:
| Name | Type | Description |
|---|---|---|
| time | uint256 | The timestamp identifying the snapshot to query. |
function snapshotTotalSupplyExact(uint256 time)
public view returns (uint256 totalSupply);Gets the total supply at an exact scheduled snapshot timestamp.
Details:
- Reverts with
SnapshotEngine_SnapshotNotFoundiftimeis not an exact scheduled snapshot.
Return Values:
| Name | Type | Description |
|---|---|---|
| supply | uint256 | The recorded total supply at the snapshot, or the current total supply if no snapshot exists for that timestamp. |
function snapshotInfo(uint256 time, address tokenHolder)
public view returns (uint256 tokenHolderBalance, uint256 totalSupply)Retrieves both an account's balance and the total supply at the snapshot for a given timestamp in a single call.
Input Parameters:
| Name | Type | Description |
|---|---|---|
| time | uint256 | The timestamp identifying the snapshot to query. |
| tokenHolder | address | The address whose balance is being requested. |
Return Values:
| Name | Type | Description |
|---|---|---|
| tokenHolderBalance | uint256 | The recorded balance of the tokenHolder at the snapshot, or current balance if no snapshot exists. |
| totalSupply | uint256 | The recorded total supply at the snapshot, or current total supply if no snapshot exists. |
function snapshotInfoBatch(uint256 time, address[] calldata addresses)
public view returns (uint256[] memory tokenHolderBalances, uint256 totalSupply)Retrieves balances of multiple accounts and the total supply at a snapshot for a given timestamp in a single call.
Input Parameters:
| Name | Type | Description |
|---|---|---|
| time | uint256 | The timestamp identifying the snapshot to query. |
| addresses | address[] | The array of addresses to query balances for. |
Return Values:
| Name | Type | Description |
|---|---|---|
| tokenHolderBalances | uint256[] | Array containing each address's balance at the snapshot, or current balance if no snapshot exists. |
| totalSupply | uint256 | The recorded total supply at the snapshot, or current total supply if no snapshot exists. |
function snapshotInfoBatch(uint256[] calldata times, address[] calldata addresses)
public view returns (uint256[][] memory tokenHolderBalances, uint256[] memory totalSupply)Retrieves balances of multiple accounts at multiple snapshots, as well as the total supply at each snapshot.
Input Parameters:
| Name | Type | Description |
|---|---|---|
| times | uint256[] | Array of timestamps identifying each snapshot to query. |
| addresses | address[] | Array of addresses to query balances for at each snapshot. |
Return Values:
| Name | Type | Description |
|---|---|---|
| tokenHolderBalances | uint256[][] | 2D array where each row corresponds to the balances of all provided addresses at a given snapshot. |
| totalSupplies | uint256[] | Array containing the total supply at each snapshot, or current supply if no snapshot exists. |
While SnapshotEngine cannot be deployed with a proxy, modules implement ERC-7201 to allow direct reuse by CMTAT deployment variants.
The toolchain includes the following components, where the versions are the latest ones that we tested:
-
Development
- npm 11.11.0
- Hardhat ^2.22.7
- Node v24.14.1
-
Compilation
-
Solidity v0.8.34
-
CMTAT v3.3.0-rc1
-
OpenZeppelin
-
- Clone the repository
Clone the git repository, with the option --recurse-submodules to fetch the submodules:
git clone git@github.com:CMTA/SnapshotEngine.git --recurse-submodules
- Node.js version
We recommend to install the Node Version Manager nvm to manage multiple versions of Node.js on your machine. You can then, for example, install the version 20.5.0 of Node.js with the following command: nvm install 20.5.0
The file .nvmrc at the root of the project set the Node.js version. nvm usewill automatically use this version if no version is supplied on the command line.
- node modules
To install the node modules required by SnapshotEngine, run the following command at the root of the project:
npm install
To use Hardhat, the recommended way is to use the version installed as part of the node modules, via the
npxcommand:
npx hardhat
Alternatively, you can install Hardhat globally:
npm install -g hardhat
See Hardhat's official documentation for more information.
You can get the size of the contract by running the following commands.
- Compile the contracts:
npx hardhat compile- Run the script:
npm run-script sizeThe script calls the plugin hardhat-contract-sizer with Hardhat.
Tests are written in JavaScript using Hardhat + Ethers and run only with Hardhat as follows:
npx hardhat test
To use the global hardhat install, use instead hardhat test.
Please see the Hardhat documentation for more information about the writing and running of Hardhat.
We use linters to ensure consistent coding style. If you contribute code, please run this following command:
For JavaScript:
npm run-script lint:js
npm run-script lint:js:fix For Solidity:
npm run-script lint:sol
npm run-script lint:sol:fixTo generate documentation with surya, you can call the three bash scripts in doc/script
| Task | Script | Command exemple |
|---|---|---|
| Generate graph | script_surya_graph.sh | npx surya graph -i contracts/**/*.sol npx surya graph contracts/SnapshotEngine.sol |
| Generate inheritance | script_surya_inheritance.sh | npx surya inheritance contracts/modules/SnapshotEngine.sol -i npx surya inheritance contracts/modules/SnapshotEngine.sol |
| Generate report | script_surya_report.sh | npx surya mdreport -i surya_report.md contracts/modules/SnapshotEngine.sol npx surya mdreport surya_report.md contracts/modules/SnapshotEngine.sol |
In the report, the path for the different files are indicated in absolute. You have to remove the part which correspond to your local filesystem.
Code coverage for Solidity smart-contracts, installed as a hardhat plugin
npm run-script coveragenpm run-script docgenPlease see SECURITY.md (CMTAT main repository).
This project is not audited !
| Version | Tool | Report | Feedback | Notes |
|---|---|---|---|---|
v0.4.0 |
Slither | slither-report.md | slither-feedback.md | Raw report plus separate project feedback. |
v0.4.0 |
Aderyn | aderyn-report.md | aderyn-feedback.md | Raw report plus separate project feedback. |
v0.3.0 |
Slither | slither-report.md | Integrated in report | Legacy format with inline feedback in the report file. |
v0.3.0 |
Aderyn | aderyn-report.md | Integrated in report | Legacy format with inline feedback in the report file. |
| Version | Tool | Report | Feedback | Notes |
|---|---|---|---|---|
v0.4.0 |
Nethermind AuditAgent | audit_agent_report_v0.4.0.pdf | audit_agent_feedback_v0.4.0.md | AI-generated scan report with separate project feedback. |
Slither is a Solidity static analysis framework written in Python3
slither . --checklist --filter-paths "openzeppelin-contracts-upgradeable|openzeppelin-contracts|@openzeppelin|test|CMTAT|mock" > slither-report.mdHere is the list of report performed with Aderyn
aderyn -x mock --output aderyn-report.mdYou can find a prototype to distribute on-chain dividend based on on-chain snapshot here:
Note that this project used snapshots performed directly inside CMTAT (internal/integrated), see CMTAT v2.4.0, not through the SnapshotEngine but the principle is similar.
The code is copyright (c) Capital Market and Technology Association, 2018-2026, and is released under Mozilla Public License 2.0.














