Skip to content

CMTA/SnapshotEngine

SnapshotEngine

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.

Repository Notes

  • 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.

Deployment Variants

  • 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 KiB
  • SnapshotEngineOwnable2Step: 7.387 KiB
  • CMTATUpgradeableInternalSnapshot: 23.963 KiB
  • CMTATStandaloneInternalSnapshot: 23.963 KiB

Table of Contents

Quick Start

npm install
npx hardhat compile
npx hardhat test

When to use it

Incident 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.

How to include it

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.

How to set it

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.

Compatibility

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

CMTAT deployment version

Internal/integrated snapshots

This repository also contains CMTAT deployment versions with the required snapshot modules integrated:

  • CMTATUpgradeableInternalSnapshot for proxy deployment.

surya_inheritance_CMTATUpgradeableInternalSnapshot.sol

  • CMTATStandaloneInternalSnapshot for non-proxy deployment (initialized through constructor).

surya_inheritance_CMTATStandaloneInternalSnapshot.sol

  • The CMTAT features are included by inheriting from the CMTAT base contract CMTATBaseRuleEngine and overriding the internal update function (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 _update function 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 external SnapshotEngine contract.
  • These local deployment variants intentionally do not include CMTATBaseSnapshot / setSnapshotEngine support. That combination pushed the deployment bytecode above the EVM smart contract size limit, so the local CMTAT*Snapshot contracts are now internal-snapshot-only variants.

CMTAT with SnapshotEngine (external)

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.

Schema

SnapshotEngine

The main contract is SnapshotEngine

Inheritance

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().

surya_inheritance_SnapshotEngine.sol

Graph

surya_graph_SnapshotEngine.sol

SnapshotEngineOwnable2Step

The alternative ownable deployment contract is SnapshotEngineOwnable2Step

Inheritance

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.

surya_inheritance_SnapshotEngineOwnable2Step.sol

Graph

surya_graph_SnapshotEngineOwnable2Step.sol

Technical

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

Complexity

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, or poke() 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

Function Schemas

Here are several schema to explain the main functions

Get next snapshot

Shows how the engine computes and returns the list of future scheduled snapshot timestamps.

SnpashotModule-Schema-getNextSnapshot.drawio

Schedule a snapshot

Shows how a new future snapshot timestamp is validated and inserted into the schedule.

SnpashotModule-Schema-scheduleSnapshot.drawio

Reschedule a snapshot

Shows how an existing scheduled snapshot is moved to a new valid timestamp.

SnpashotModule-Schema-rescheduleSnapshot.drawio

Unschedule a snapshot

Shows how a scheduled snapshot is removed from the schedule.

SnpashotModule-Schema-unscheduleSnapshot.drawio

Access Control

Two authorization models are available depending on deployment:

  • SnapshotEngine: role-based access control via SNAPSHOOTER_ROLE.
  • SnapshotEngineOwnable2Step: owner-only access via onlyOwner.

Integrated CMTAT snapshot deployments use CMTAT role-based access control for snapshot scheduling functions.

RBAC Role list

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

ERC-20 token bound

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.

Events

The snapshot-related events are defined once in ISnapshotBase and emitted by the shared SnapshotBase logic.

SnapshotEngine and SnapshotEngineOwnable2Step

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.

CMTATUpgradeableInternalSnapshot and CMTATStandaloneInternalSnapshot

These integrated CMTAT deployments emit the same snapshot events through their inherited snapshot base logic:

  • SnapshotSchedule(oldTime, newTime)
  • SnapshotUnschedule(time)
  • SnapshotMaterialized(time, blockNumber)

Notes

  • 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.

Ethereum API

SnapshotBase

Base contract for snapshot engines, providing common errors and read-only functions to query snapshots.


Events

SnapshotSchedule(uint256, uint256)
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)
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.

Errors

SnapshotEngine_SnapshotScheduledInThePast(uint256, uint256)
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, uint256)
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, uint256)
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,uint256)
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.

SnapshotEngine_SnapshotAlreadyExists()

Thrown when attempting to schedule a snapshot that already exists.


SnapshotEngine_SnapshotAlreadyDone()

Thrown when attempting to execute or schedule a snapshot that has already been taken.


SnapshotEngine_NoSnapshotScheduled()

Thrown when attempting to unschedule or interact with a snapshot when no snapshot is currently scheduled.


SnapshotEngine_SnapshotNotFound()

Thrown when querying or modifying a snapshot that cannot be found.


Functions

getAllSnapshots() -> (uint256[] memory)

Get all snapshots that have been created.

Return Values:

Name Type Description
snapshots uint256[] Array of timestamps of all existing snapshots.

getNextSnapshots() -> (uint256[] memory)

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.

SnapshotScheduler

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.

surya_inheritance_SnapshotSchedulerModule.sol

SnapshotSchedulerModuleUML


Functions

poke()
function poke() public

Materializes 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_ROLE for SnapshotEngine, onlyOwner for SnapshotEngineOwnable2Step.

scheduleSnapshot(uint256)
function scheduleSnapshot(uint256 time) 
public

Schedules 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.

scheduleSnapshotNotOptimized(uint256)
function scheduleSnapshotNotOptimized(uint256 time) 
public

Schedules 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.

rescheduleSnapshot(uint256 oldTime, uint256 newTime)
function rescheduleSnapshot(uint256 oldTime,uint256 newTime) 
public

Reschedules 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.

unscheduleLastSnapshot(uint256 time)
function unscheduleLastSnapshot(uint256 time) 
public

Cancels 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.

unscheduleSnapshotNotOptimized(uint256 time)
function unscheduleSnapshotNotOptimized(uint256 time) 
public

Cancels 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.

SnapshotState

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.

surya_inheritance_SnapshotStateModule.sol

SnapshotStateModuleUML


Functions

snapshotBalanceOf(uint256, address) -> (uint256)
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.

snapshotBalanceOfExact(uint256, address) -> (uint256)
function snapshotBalanceOfExact(uint256 time, address tokenHolder)
public view returns (uint256 tokenHolderBalance);

Gets the balance at an exact scheduled snapshot timestamp.

Details:

  • Reverts with SnapshotEngine_SnapshotNotFound if time is not an exact scheduled snapshot.

snapshotTotalSupply(uint256) -> (uint256)
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.

snapshotTotalSupplyExact(uint256) -> (uint256)
function snapshotTotalSupplyExact(uint256 time)
public view returns (uint256 totalSupply);

Gets the total supply at an exact scheduled snapshot timestamp.

Details:

  • Reverts with SnapshotEngine_SnapshotNotFound if time is 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.

snapshotInfo(uint256, address) -> (uint256, uint256)
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.

snapshotInfoBatch(uint256, address[]) -> (uint256[], uint256)
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.

snapshotInfoBatch(uint256[], address[]) -> (uint256, uint256)
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.

VersionModule

surya_inheritance_VersionModule.sol

Storage management (ERC-7201)

While SnapshotEngine cannot be deployed with a proxy, modules implement ERC-7201 to allow direct reuse by CMTAT deployment variants.

Usage instructions

Dependencies

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

      • OpenZeppelin Contracts (Node.js module) v5.6.1

      • OpenZeppelin Contracts Upgradeable (Node.js module) v5.6.1 (to compile CMTAT)

Installation

  • 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

Hardhat

To use Hardhat, the recommended way is to use the version installed as part of the node modules, via the npx command:

npx hardhat

Alternatively, you can install Hardhat globally:

npm install -g hardhat

See Hardhat's official documentation for more information.

Contract size

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 size

The script calls the plugin hardhat-contract-sizer with Hardhat.

Testing

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.

Code style guidelines

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:fix

Generate documentation

To 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 coverage

Docgen (Solidity API)

npm run-script docgen

Security

Vulnerability disclosure

Please see SECURITY.md (CMTAT main repository).

Audit

This project is not audited !

Audit report summary

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.

AI Auditing tool

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.

Tools

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.md

Aderyn

Here is the list of report performed with Aderyn

aderyn -x mock --output aderyn-report.md

Further reading

You 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.

Intellectual property

The code is copyright (c) Capital Market and Technology Association, 2018-2026, and is released under Mozilla Public License 2.0.

About

The SnapshotEngine is a smart contract system designed to perform ERC-20 on-chain snapshots, making it easier to distribute dividends or other token-based rewards directly on-chain

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Contributors