Smart Contracts of the Forta Network This repo uses Hardhat as a development environment.
Forta is a decentralized, community-based monitoring network to detect threats and anomalies on DeFi, NFT, governance, bridges and other Web3 systems in real-time.
Given timely and relevant alerts about the security and health of owned or dependent systems, protocols and investors can react quickly to neutralize threats and prevent or minimize loss of funds.
Forta comprises a decentralized network of independent node operators that scan all transactions and block-by-block state changes for outlier transactions and threats. When an issue is detected, node operators send alerts to subscribers of potential risks, which enables them to take action
Leveraging Forta, developers can build detection bots and machine learning models, and run them on the decentralized Forta network to uncover anomalous activity on every blockchain transaction.
The contracts coordinate and govern Forta Network's Detection Bots (formerly Agents) and Scanner Pools.
FORT is the ERC-20 Token of the Forta Network. It acts as:
- Governance token (see our path to decentralization)
- Network security mechanism via staking and slashing on participating subjects (Bots and Scanner Nodes).
- Network rewards.
FORT is deployed on Ethereum Mainnet and bridged to Polygon using Polygon's PoS Bridge.
Contract responsible of Agent registration, updates, enabling, disabling and defining the Staking Threshold for agents.
Agents are identified by uint256(keccak256(UUIDv4))
Compliant with ERC-721 standard.
Contract responsible of Scanner Node registration, updates, enabling, disabling and defining the Staking Threshold for Scanner Nodes.
Scanners are identified by their EOA Address casted to uint256
Compliant with ERC-721 standard.
A Scan
Contract responsible of Scanner Pool crNode registration, updates, enabling, disabling and defining the Staking Threshold for Scanner Nodes.
Scanners are identified by their EOA Address casted to uint256
Compliant with ERC-721 standard.
Register of the assignments of Agents and Scanners, governed by the Assigner Software (off chain).
Contract handling staking of FORT tokens on subjects (participant of the network), slashing and reward distribution. Deposited stake is represented by ERC-1155 shares, for active and inactive (withdrawal initiated, non-transferrable) stake. Share ID is derived from the subject type, subject ID and it being active or inactive.
These contracts handle stake delegation for Scanner Pools and reward distribution between pool owner and delegators.
Holds the accepted Scanner Node software image IPFS hash. A change in the version will trigger Scanner Node autoupdate. New versions will be proposed by governance.
Access Control Singleton for all contracts except Token and VestingWallets
Meta tx contract, based on the Permit Singleton.
Vesting contracts. Can bridge tokens to Polygon to a StakingEscrow
contract destination so they can participate on staking.
Contracts that allow vested token holders to stake on FortaStaking
- Interface implementations previously deployed are under
_old
folders, for upgrade testing. - Contracts no longer in use are under
_deprecated
folder.
yarn
npm run test
You can query a network deployment with the help of the ./scripts/releases/deployments/<network_id>.json
files and using ethers to attach contract factories to addresses.
There is a helper function to automate this process from console or scripts called loadEnv()
Example:
npx hardhat --network <network name from hardhat.config.js> console
> const e = require('./scripts/loadEnv')
> const d = await e.loadEnv()
> await d.contracts.scannerPool.ownerOf('1')
Deployment addresses are listed in scripts/.cache-<chainID>.json
To deploy the platform's contracts last version, as used by the tests (except ScannerNodeVersion
):
npx hardhat run --network <network> scripts/deployments/deploy-platform.js
To see debug logs, we are using debug package
DEBUG=* npx hardhat run --network <network> scripts/deploy-platform.js
Read our docs for our CI/CD contracts pipeline using Github Actions and Openzeppelin Defender.
Implemented by Raúl Martínez Based in the concept repo by Santiago Palladino
Also covered in the (above pipeline!)[https://github.com/forta-network/forta-contracts/blob/master/DEPLOYMENT_AND_ADMIN_ACTIONS.md]
This network is under active development, so most of the components are UUPSUpgradeable
and new implementations are deployed from time to time.
Upgrading a contract is a process with risk, storage layout collision might happen. We follow good practices for writing upgradeable contracts, using storage __gaps to "reserve" 50 storage slots per contract initially. The process to develop, test and deploy new implementations are as follow:
- Ensure that the contract inherits from
IVersioned
(if it isBaseControllerUpgradeable
it will). - Bump the version accordingly. Contract versioning should follow semver and increment with each upgrade or deployment.
- Develop according to good practices, modify __gap length to accommodate new state, add comments with storage accounting in contract variables and __gap values.
- Write an upgradeability test in
test/components/upgrades.test.js
.- Deploy an upgradeable proxy with the base implementation, found in
components/_old/<component_folder>/<ComponentName>_X_Y_Z
. If not there, either:- Use
hardhat flatten
to generate a file with all the inheriting contracts. - Remove duplicate licenses and pragma statements.
- Rename main component in flattened file to
<ComponentName>_X_Y_Z
. - If the contracts is deployed and the repo is not tagged and you are not sure if the exact files are in the current commit, generate a flattened version obtained from verified contracts in relevant block explorer (Etherscan, Polyscan...) and add it.
- Use
- Initialize all the state of the contract, assert the values are there.
- Upgrade to the new implementation
- Check the output from
upgrades-plugin
, if there is any problems fix them. Check the plugin docs and faqs if needed. - Assert the values of the state have not changed.
- Deploy an upgradeable proxy with the base implementation, found in
- Execute the script
scripts/storageToTable
to print a markdown table of the before and after implementations- You may need to execute
test/components/upgrades.test.js
withit.only(<relevant_test>)
so both old and new implementations are present in.openzeppelin/unknown-31337.json
. Delete delete.openzeppelin/unknown-31337.json
before executing the test sostorageToTable
finds the correct implementations.
- You may need to execute
- Visualize and assert that the sum of the storage reserved is the same before and after, and that the new gaps are adjusted correctly. If the test in 4.5 passes this is probably correct, if that test fails, something will not add up.
- Write a script to deploy the upgrade and execute the initializing methods needed.
- Add a flattened version of the deployed implementation to
components/_old/<component_folder>/<ComponentName>_X_Y_Z
.
Forta contracts use Solidity's Custom Errors instead of require(<test>, '<Error String>')
.
Some other parts of the system do not support this feature in ABIs yet. To generate compatible ABIs:
- Compile contracts
npx hardhat run scripts/abis-without-custom-errors.js
- Check
.abis-no-errors
folder.
- Open
scripts/matic/enter.js
and editAMOUNT
to set the FORT to mint and bridge npx hardhat run --network goerli scripts/matic/enter.js
- Eventually, Polygon PoS Bridge will send the tokens to your wallet. It may take a while (10+ mins).
- To stake, call
deposit(uint8 subjectType, uint256 subject, uint256 stakeValue)
inFortaStaking
instance (checkscripts/.cache-80001.json -> staking -> address
)
We have a bug bounty program on Immunefi. Please report any security issues you find through the Immunefi dashboard, or reach out to (tech@forta.org)[mailto:tech@forta.org]