Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2

- name: Setup NodeJS 20.5.0
uses: actions/setup-node@v3
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 #v4.4.0
with:
node-version: 20.5.0

Expand All @@ -28,4 +28,4 @@ jobs:
run: npm install

- name: Run Hardhat Test
run: npx hardhat test
run: npx hardhat test
3 changes: 3 additions & 0 deletions .solcover.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
skipFiles: ['openzeppelin-contracts-upgradeable', 'mocks', '*/test/*']
};
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,17 @@ Please follow <https://changelog.md/> conventions.

- Update changelog

## 0.2.0

- Dependencies
- Update CMTAT to [v3.0.0-rc7](https://github.com/CMTA/CMTAT/releases/tag/v3.0.0-rc2)
- Update OpenZeppelin library to [v5.4.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v5.4.0)
- Update Solidity to 0.8.30
- Update EVM version to Prague

- Better code separation trough the creation of modules

- Use [ERC-7201](https://eips.ethereum.org/EIPS/eip-7201) for storage for compatibility with CMTAT

## 0.1.0

Expand Down
2 changes: 1 addition & 1 deletion CMTAT
Submodule CMTAT updated 1061 files
550 changes: 529 additions & 21 deletions README.md

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,5 @@

## Reporting a Vulnerability

To report a security vulnerability in CMTAT Factory, please see instruction in [CMTA/CMTAT/SECURITY.md](https://github.com/CMTA/CMTAT/blob/master/SECURITY.md)

To report a security vulnerability in this project, please see instruction in [CMTA/CMTAT/SECURITY.md](https://github.com/CMTA/CMTAT/blob/master/SECURITY.md)

153 changes: 18 additions & 135 deletions contracts/SnapshotEngine.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,34 @@

pragma solidity ^0.8.20;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/* ==== OpenZeppelin === */
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
/* ==== Modules === */
import {SnapshotScheduler} from "./modules/SnapshotScheduler.sol";
import {SnapshotState} from "./modules/SnapshotState.sol";
import {VersionModule} from "./modules/VersionModule.sol";
/* ==== Interfaces and library === */
import {SnapshotBase} from "./library/SnapshotBase.sol";
import {ISnapshotState} from "./interface/ISnapshotState.sol";
import {ISnapshotEngine} from "../CMTAT/contracts/interfaces/engine/ISnapshotEngine.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Errors} from "./library/Errors.sol";
contract SnapshotEngine is SnapshotBase, AccessControl, ISnapshotEngine, ISnapshotState {
/* ============ State Variables ============ */
bytes32 public constant SNAPSHOOTER_ROLE = keccak256("SNAPSHOOTER_ROLE");
/// @notice token contract
bytes32 public constant TOKEN_CONTRACT_ROLE =
keccak256("TOKEN_CONTRACT_ROLE");
/**
* @notice
* Get the current version of the smart contract
*/
string public constant VERSION = "0.1.0";
IERC20 immutable erc20;
contract SnapshotEngine is SnapshotState, SnapshotScheduler, VersionModule, ISnapshotEngine {
/* ==== Modifier === */
modifier onlyBoundToken() {
if (_msgSender() != address(erc20)) {
revert Errors.SnapshotEngine_UnauthorizedCaller();
}
_;
}

/* ============ Constructor ============ */
constructor(IERC20 erc20_, address admin) {
require(address(erc20_) != address(0), Errors.SnapshotEngine_AddressZeroNotAllowedForERC20());
require(admin != address(0), Errors.SnapshotEngine_AddressZeroNotAllowedForAdmin());
erc20 = erc20_;
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(TOKEN_CONTRACT_ROLE, address(erc20_));
}



/*//////////////////////////////////////////////////////////////
PUBLIC/EXTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
Expand All @@ -52,11 +50,10 @@ contract SnapshotEngine is SnapshotBase, AccessControl, ISnapshotEngine, ISnapsh
/*//////////////////////////////////////////////////////////////
ERC-20 ENTRY POINT
//////////////////////////////////////////////////////////////*/
/**
* @dev Update balance and/or total supply snapshots before the values are modified. This is implemented
* in the _beforeTokenTransfer hook, which is executed for _mint, _burn, and _transfer operations.
/**
* @inheritdoc ISnapshotEngine
*/
function operateOnTransfer(address from, address to, uint256 balanceFrom, uint256 balanceTo, uint256 totalSupply) public override onlyRole(TOKEN_CONTRACT_ROLE) {
function operateOnTransfer(address from, address to, uint256 balanceFrom, uint256 balanceTo, uint256 totalSupply) public override onlyBoundToken() {
SnapshotBase._setCurrentSnapshot();
if (from != address(0)) {
// for both burn and transfer
Expand All @@ -74,118 +71,4 @@ contract SnapshotEngine is SnapshotBase, AccessControl, ISnapshotEngine, ISnapsh
SnapshotBase._updateTotalSupplySnapshot(totalSupply);
}
}


/*//////////////////////////////////////////////////////////////
GET SNAPSHOT STATE
//////////////////////////////////////////////////////////////*/
/**
* @notice Return snapshotBalanceOf and snapshotTotalSupply to avoid multiple calls
* @return ownerBalance , totalSupply - see snapshotBalanceOf and snapshotTotalSupply
*/
function snapshotInfo(uint256 time, address owner) public view returns (uint256 ownerBalance, uint256 totalSupply) {
ownerBalance = snapshotBalanceOf(time, owner);
totalSupply = snapshotTotalSupply(time);
}

/**
* @notice Return snapshotBalanceOf for each address in the array and the total supply
* @return ownerBalances array with the balance of each address, the total supply
*/
function snapshotInfoBatch(uint256 time, address[] calldata addresses) public view returns (uint256[] memory ownerBalances, uint256 totalSupply) {
ownerBalances = new uint256[](addresses.length);
for(uint256 i = 0; i < addresses.length; ++i){
ownerBalances[i] = snapshotBalanceOf(time, addresses[i]);
}
totalSupply = snapshotTotalSupply(time);
}

/**
* @notice Return snapshotBalanceOf for each address in the array and the total supply
* @return ownerBalances array with the balance of each address, the total supply
*/
function snapshotInfoBatch(uint256[] calldata times, address[] calldata addresses) public view returns (uint256[][] memory ownerBalances, uint256[] memory totalSupply) {
ownerBalances = new uint256[][](times.length);
totalSupply = new uint256[](times.length);
for(uint256 iT = 0; iT < times.length; ++iT){
(ownerBalances[iT], totalSupply[iT]) = snapshotInfoBatch(times[iT],addresses);
}
}

/**
* @notice Return the number of tokens owned by the given owner at the time when the snapshot with the given time was created.
* @return value stored in the snapshot, or the actual balance if no snapshot
*/
function snapshotBalanceOf(
uint256 time,
address owner
) public view returns (uint256) {
return SnapshotBase._snapshotBalanceOf(time, owner, erc20.balanceOf(owner));
}

/**
* @dev See {OpenZeppelin - ERC20Snapshot}
* Retrieves the total supply at the specified time.
* @return value stored in the snapshot, or the actual totalSupply if no snapshot
*/
function snapshotTotalSupply(uint256 time) public view returns (uint256) {
return SnapshotBase._snapshotTotalSupply(time, erc20.totalSupply());
}

/*//////////////////////////////////////////////////////////////
RESTRICTED FUNCTIONS
//////////////////////////////////////////////////////////////*/

/**
* @notice
* Schedule a snapshot at the given time specified as a number of seconds since epoch.
* The time cannot be before the time of the latest scheduled, but not yet created snapshot.
*/
function scheduleSnapshot(uint256 time) public onlyRole(SNAPSHOOTER_ROLE) {
SnapshotBase._scheduleSnapshot(time);
}

/**
* @notice
* Schedule a snapshot at the given time specified as a number of seconds since epoch.
* The time cannot be before the time of the latest scheduled, but not yet created snapshot.
*/
function scheduleSnapshotNotOptimized(
uint256 time
) public onlyRole(SNAPSHOOTER_ROLE) {
SnapshotBase._scheduleSnapshotNotOptimized(time);
}

/**
* @notice
* Reschedule the scheduled snapshot, but not yet created snapshot with the given oldTime to be created at the given newTime specified as a number of seconds since epoch.
* The newTime cannot be before the time of the previous scheduled, but not yet created snapshot, or after the time fo the next scheduled snapshot.
*/
function rescheduleSnapshot(
uint256 oldTime,
uint256 newTime
) public onlyRole(SNAPSHOOTER_ROLE) {
SnapshotBase._rescheduleSnapshot(oldTime, newTime);
}

/**
* @notice
* Cancel creation of the scheduled snapshot, but not yet created snapshot with the given time.
* There should not be any other snapshots scheduled after this one.
*/
function unscheduleLastSnapshot(
uint256 time
) public onlyRole(SNAPSHOOTER_ROLE) {
SnapshotBase._unscheduleLastSnapshot(time);
}

/**
* @notice
* Cancel creation of the scheduled snapshot, but not yet created snapshot with the given time.
*/
function unscheduleSnapshotNotOptimized(
uint256 time
) public onlyRole(SNAPSHOOTER_ROLE) {
SnapshotBase._unscheduleSnapshotNotOptimized(time);
}
}
88 changes: 88 additions & 0 deletions contracts/interface/ISnapshotBase.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//SPDX-License-Identifier: MPL-2.0

pragma solidity ^0.8.20;

/// @title ISnapshotBase
/// @notice Base interface for snapshot engines, providing common errors and read-only functions to query snapshots.
interface ISnapshotBase {
/* ============ Events ============ */
/**
* @notice Emitted when a snapshot is scheduled for the first time or rescheduled.
* @param oldTime The previous scheduled timestamp (0 if newly scheduled).
* @param newTime The new scheduled timestamp for the snapshot.
*/
event SnapshotSchedule(uint256 indexed oldTime, uint256 indexed newTime);

/**
* @notice Emitted when a previously scheduled snapshot is canceled.
* @param time The timestamp of the snapshot that was unscheduled.
*/
event SnapshotUnschedule(uint256 indexed time);

/* ============ Erros ============ */
/**
* @notice Thrown when attempting to schedule a snapshot at a time earlier than the current block timestamp.
* @param time The snapshot time requested.
* @param timestamp The current block timestamp.
*/
error SnapshotEngine_SnapshotScheduledInThePast(
uint256 time,
uint256 timestamp
);
/**
* @notice Thrown when a snapshot timestamp is earlier than the last snapshot timestamp.
* @param time The snapshot time requested.
* @param lastSnapshotTimestamp The timestamp of the most recent snapshot.
*/
error SnapshotEngine_SnapshotTimestampBeforeLastSnapshot(
uint256 time,
uint256 lastSnapshotTimestamp
);
/**
* @notice Thrown when a snapshot timestamp is later than the next scheduled snapshot timestamp.
* @param time The snapshot time requested.
* @param nextSnapshotTimestamp The timestamp of the next scheduled snapshot.
*/
error SnapshotEngine_SnapshotTimestampAfterNextSnapshot(
uint256 time,
uint256 nextSnapshotTimestamp
);
/**
* @notice Thrown when a snapshot timestamp is earlier than the previous snapshot timestamp.
* @param time The snapshot time requested.
* @param previousSnapshotTimestamp The timestamp of the previous snapshot.
*/
error SnapshotEngine_SnapshotTimestampBeforePreviousSnapshot(
uint256 time,
uint256 previousSnapshotTimestamp
);
/**
* @notice Thrown when attempting to schedule a snapshot that already exists.
*/
error SnapshotEngine_SnapshotAlreadyExists();
/**
* @notice Thrown when attempting to execute or schedule a snapshot that has already been taken.
*/
error SnapshotEngine_SnapshotAlreadyDone();
/**
* @notice Thrown when attempting to unschedule or interact with a snapshot when no snapshot is currently scheduled.
*/
error SnapshotEngine_NoSnapshotScheduled();
/**
* @notice Thrown when querying or modifying a snapshot that cannot be found.
*/
error SnapshotEngine_SnapshotNotFound();

/* ============ Functions ============ */
/**
* @notice Get all snapshots that have been created.
* @return An array of timestamps representing all existing snapshots.
*/
function getAllSnapshots() external view returns (uint256[] memory);

/**
* @notice Get the next scheduled snapshots that have not yet been created.
* @return An array of timestamps representing all future scheduled snapshots.
*/
function getNextSnapshots() external view returns (uint256[] memory);
}
47 changes: 47 additions & 0 deletions contracts/interface/ISnapshotScheduler.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @title ISnapshotScheduler
/// @notice Interface for scheduling, rescheduling, and canceling snapshots
interface ISnapshotScheduler {
/**
* @notice Schedule a snapshot at the given time specified as a number of seconds since epoch.
* @dev The time cannot be before the latest scheduled but not yet created snapshot.
* Access restricted to accounts with SNAPSHOOTER_ROLE.
* @param time The scheduled time of the snapshot.
*/
function scheduleSnapshot(uint256 time) external;

/**
* @notice Schedule a snapshot at the given time specified as a number of seconds since epoch (non-optimized version).
* @dev The time cannot be before the latest scheduled but not yet created snapshot.
* Access restricted to accounts with SNAPSHOOTER_ROLE.
* @param time The scheduled time of the snapshot.
*/
function scheduleSnapshotNotOptimized(uint256 time) external;

/**
* @notice Reschedule a snapshot from oldTime to newTime.
* @dev The new time cannot be before the previous scheduled snapshot
* or after the next scheduled snapshot.
* Access restricted to accounts with SNAPSHOOTER_ROLE.
* @param oldTime The original scheduled time of the snapshot.
* @param newTime The new scheduled time of the snapshot.
*/
function rescheduleSnapshot(uint256 oldTime, uint256 newTime) external;

/**
* @notice Cancel creation of the last scheduled snapshot at the given time.
* @dev There must not be any other snapshots scheduled after this one.
* Access restricted to accounts with SNAPSHOOTER_ROLE.
* @param time The scheduled time of the snapshot to cancel.
*/
function unscheduleLastSnapshot(uint256 time) external;

/**
* @notice Cancel creation of the scheduled snapshot at the given time (non-optimized version).
* @dev Access restricted to accounts with SNAPSHOOTER_ROLE.
* @param time The scheduled time of the snapshot to cancel.
*/
function unscheduleSnapshotNotOptimized(uint256 time) external;
}
Loading