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 Nodes.
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.
Register of the assignments of Agents and Scanners, governed by the Assigner Software.
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.
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
Observer pattern allowing actions in contracts to have side effects. Currently unused.
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
_oldfolders, for upgrade testing. - Contracts no longer in use are under
_deprecatedfolder.
Deployment addresses are listed in scripts/.cache-<chainID>.json
To deploy the platform's contracts:
npx hardhat run --network <network> scripts/deploy-platform.js
To see debug logs, we are using debug package
DEBUG=* npx hardhat run --network <network> scripts/deploy-platform.js
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 isBaseControllerUpgradeableit will). - Bump the version accordingly.
- 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. 4.1 Deploy an upgradeable proxy with the base implementation, found incomponents/_old/<component_folder>/<ComponentName>_X_Y_Z. If not there, either:- Use
hardhat flattenerto generate one. - 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.
- Rename main component in flattened file to
<ComponentName>_X_Y_Z. 4.2 Initialize all the state of the contract, assert the values are there. 4.3 Upgrade to the new implementation 4.4 Check the output fromupgrades-plugin. If gap sizes changed, you may need to activate the flagunsafeSkipStorageCheckto deploy. 4.5 Assert the values of the state have not changed.
- Use
- Execute the script
scripts/storageToTableto print a markdown table of the before and after implementations- You may need to execute
test/components/upgrades.test.jswithit.only(<relevant_test>)so both old and new implementations are present in.openzeppelin/unknown-31337.json. Delete delete.openzeppelin/unknown-31337.jsonbefore executing the test sostorageToTablefinds 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-errorsfolder.
- Make sure the wallets in
.envhasWHITELIST_ROLEin Goerli and Mumbai andMINTER_ROLEin Goerli (seeenv.examplefor reference). FORT will be sent in Mumbai to the same wallet you provided in.env -> GOERLI_MNEMONIC` - Open
scripts/matic/enter.jsand editAMOUNTto 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)inFortaStakinginstance (checkscripts/.cache-80001.json -> staking -> address)
