Introduction
High level overview of the Forte Rules Engine
Purpose
This document offers a high level overview of the Forte rules engine - what it does and how it works. Read this to gain an initial understanding of the repo and the structure of the Forte rules engine.
Glossary
Term | Definition |
---|---|
AppManager | An appliction-associated smart contract acting as a central hub for managing the application it is associated with. Allows for creation/maintenance of permission roles, application rules, pause windows, and user account metadata. Can be utilized for both updating and checking an account’s role in relation to the application. |
ApplicationHandler | An application-associated smart contract supporting the AppManager contract by storing the application level rules data and functions. This is the connection to the Forte Rules Engine for the AppManager, assessesing and reading the rules that are activated for an application. |
AssetHandler | The Protocol Asset Handler Diamond serves as the access point to the protocol for a protocol supported asset. The protocol supported asset stores the Handler Diamond proxy address and uses it to call the check all rules function . The Handler Diamond stores all asset level rule data, rule activation status, and connects the token to the AppManager for role based access control. |
Pricing Module | Application-associated smart contract(s) serving as token-price data sources for ecosystem applications. The pricing module can be found in 2 different categories: ERC20 and ERC721. Any custom-made pricing contract that intends to be protocol compliant must implement the IProtocolERC20Pricing and/or IProtocolERC721Pricing interface(s). |
Protocol Supported ERC721 | An ERC721 token that implements the protocol IProtocolToken and contains the protocol hook. |
Protocol Supported ERC20 | An ERC20 token that implements the protocol IProtocolToken and contains the protocol hook. |
Access-Level Provider | An external provider that rates or segments users based on external criteria for access level solutions. Default access level mechanisms allow developers to set user access levels. |
Permission Roles | Roles used by AppManager. They include: Admin, Access Level Admin, Risk Admin, Rule Admin, and Treasury Account. |
Application Rule | Rule applied to all protocol supported assets. They are created using the protocol’s RuleProcessorDiamond and applied in the application’s AppManager. |
Token Specific Rule | Rule applied to a specific protocol supported entity. They are created using the protocol’s RuleProcessorDiamond and applied in the token’s Handler. |
Tag | Bytes32 strings that can be attached to accounts via AppManager. Think of it as labels or badges that accounts can have. |
Diamond Pattern
This protocol uses the diamond pattern. The diamond pattern allows the handler to add new features and improvements through the use of an upgradeable proxy contract. New facet contracts can be deployed and connected to the diamond via a specialized function called diamondCut. New facets and functions allow the handler to grow while maintaining address immutability with the proxy contract. Calling contracts will only need to set the address of the diamond proxy at deployment, without having to worry about that address changing over time. The Handler Diamond follows ERC-2535 standards for storage and functions.
ERC-2535: Diamond Proxies
Architecture Overview
The following diagram outlines the general architecture of the protocol. Every application ecosystem may reuse the purple contracts. These contracts are also in a configuration that allows for upgradability. Each ecosystem must deploy the green contracts.
The following diagram outlines a sample deployment:
How Rules Work
The protocol allows rule administrators to add rules to the Rule Processor Diamond. The rule id generated by the protocol is then used within an application to apply to user economic actions. The rules are immutable and cannot be removed or modified at the protocol level once created and assigned a rule id. A rule administator may add a new rule to the Rule Processor diamond at any time. The protocol will return a rule id that is used by the application to set the rule within each applicable handler contract.
Token level rules are applied in token handlers and application level rules in the application handler. Each rule’s documentation explains the scope of that rule and the level in which it applies. Within the handler contract is a set rule function that receives the rule id for each applicable rule for that handler. A token handler does not have an activate function for application level rules and vice versa. The set rule function sets the rule per applicable economic action type. Each rule will only apply to the action types set in the activate rule function. Multiple action types may be used for each rule by sending all the desired types to the set rule function.
Rule application is a two step process. First, the rule admin must obtain a rule id, either from creating a rule or using an already created rule. Then, they must apply the rule id to the applicable handler through the appropriate set rule function. Once the rule is added to the protocol and set in the handler, all actions associated with that rule will be validated through the protocol using the provided rule parameters. The application developer sets the rule applicability and may activate and deactivate as needed. One or many rules may be active at a time.
Rule deactivation is done by sending a “false” boolean along with the no longer desired action types to the activate function. When rule active status is set to “false”, the action type will not be validated through the protocol. Rule admins can check the activation status of a rule at any time. Within the handler contracts are functions that return the activation status of the rule. The rule id for each rule and action type may be retrieved through the get rule id functions in each handler.
Rule Applicability
Rule | Level | Actions |
---|---|---|
Pause | Application | mint burn buy sell transfer |
Account Max Value by Risk Score | Application | mint buy transfer |
Account Max Tx Value by Risk Score | Application | mint buy sell transfer |
Account Max Value by Access Level | Application | mint buy transfer |
Account Max Value Out by Access Level | Application | burn sell transfer |
Account Deny For No Access Level | Application | mint burn buy sell transfer |
Oracle | ERC20/ERC721 | mint buy sell transfer |
Account Min/Max Token Balance | ERC20/ERC721 | mint burn buy sell transfer |
Account Max Trade Size | ERC20/ERC721 | buy sell |
Token Max Trading Volume | ERC20/ERC721 | mint buy sell transfer |
Token Max Buy/Sell Volume | ERC20/ERC721 | buy sell |
Token Max Supply Volatility | ERC20/ERC721 | mint burn |
Token Max Daily Trades | ERC721 | mint buy sell transfer |
Token Minimum Hold Time | ERC721 | burn sell transfer |
Token Minimum Transaction Size | ERC20 | mint burn buy sell transfer |
Rule Applicibility Controls
There are several mechanisms to control whether a rule applies to a particular user’s transactions: tags, access levels, and risk scores.
Protocol Tags Structure
Purpose
Tags are assigned to addresses by application administrators through the application manager contract. A maximum of 10 Tags per address are stored as bytes32 in the Tags data contract. This data contract is deployed when the app manager is deployed. The Tags data contract can be migrated to a new application manager during an upgrade to maintain tagged address data. App administrators can migrate data contracts to a new app manager through a two step migration process.
The protocol uses tags to assess fees and perform rule checks for tag-based rules. A user’s transactions will be subject to tag-based rules based on the tags assigned to that user. For example, users with “TagA” may have a max balance limit of 1000 protocol supported tokens where users with “TagB” may have a 10,000 token limit, and users with “TagC” may have no limit at all.
Rules may utilize a “blank tag” where no specific tag is provided to the protocol when the rule is created. These rules will apply to all users of the protocol supported token that do not have a tag assigned to them. For example, if an Account Min/Max Token Balance rule is active with a blank tag, every user that is not assigned a tag for that rule will be subject to the minimum and maximum limits of that rule.
Tags are also used for the assessment of fees within the protocol. When activated, fees are additive and will be assessed for each tag an address has stored.
Scope
Tags can be applied to individual accounts or addresses of contracts. Tags are used to assess fees or facilitate tagged rule checks throughout the protocol. When an account (user) is tagged, they will be subject to all rules that are active that utilize that tag.
see TAGGED-RULES
Enabling/Disabling
- Tags can only be added in the app manager by an app administrator.
- Tags can only be removed in the app manager by an app administrator.
Access Level Structure
Access Levels can be assigned to addresses by Access Level Administrators through the AppManager. They are predefined as 0,1,2,3,4 and are stored as uint8 in the AccessLevels data contract. This data contract is deployed when the AppManager is deployed. The AccessLevels data contract can be migrated to a new AppManager during an upgrade to maintain access level and address data. Access Level administrators can migrate data contracts to a new AppManager through a two step migration process.
The protocol uses access levels to perform access-level-based rule checks.
The default access level for each account is 0.
Scope
Access Levels are applied to addresses. When an access-level-based rule is activated, all users will be subject to any limitations set by the rule related to their individual access level.
see ACCESS-LEVEL-RULES
External Access Level Provider
An external access level provider may be utilized when you need to rely on controls and data passports provided by external systems. In order to switch to an external access level provider, the external provider contract must conform to the IAccessLevels interface or an adapter contract that conforms to the interface must be used. Once the external provider contract is deployed, the AppManager must be pointed to the new provider.
Risk Scores
Purpose
Risk administrators may assign addresses with a risk score via the application manager contract. These scores range from 0-99. Risk administrators are the only admins who can assign risk scores to addresses. These risk scores facilitate the protocol’s risk rule checks. The application will pass the risk score, rule id, and value of the transaction to the protocol for risk rule evaluations.
Rule administrators can add and activate RISK-RULES via the application handler contract. These rules are applied at the application level, meaning all assets within the application will be subjected to these rules.
Risk rules are dependant on the application having deployed pricing contracts and connected to the application handler. The protocol will then check that the transaction is valid for the user’s assigned risk score.
Scope
Risk scores can be applied to individual accounts or addresses of contracts. Risk scores are used to facilitate risk rule checks throughout the protocol. When an account (user) is given a risk score, they will be subject to any risk rules that are active. When a risk rule is added and activated every user with a risk score will be subjected to this risk rule. This means if the Account Max Tx Value by Risk Score is active and a has the following rule values:
The max values per period will be as follows:
risk score | balance | resultant logic |
---|---|---|
Implied* | Implied* | 0-24 -> NO LIMIT |
25 | $500 | 25-49 -> $500 max |
50 | $250 | 50-74 -> $250 max |
75 | $50 | 75-100 -> $50 max |
see RISK-RULES
Enabling/Disabling
- Risk scores can only be added in the app manager by an risk administrator.
- Risk scores can only be removed in the app manager by an risk administrator.
Application Handler
Purpose
The Application Handler supports the Application Manager by storing the application level rules data and functions. Procotol supported asset handler contracts call the check-application-level-rules function
via the Application Manager. The Application Manager then checks the associated Application Handler where application level rule data is stored. The Application Handler contract also serves as the Application Manager’s connection to the protocol rule processor diamond for the application level rules.
Application Level Rules
Application level rules apply to all assets associated to the Application Manager and handler when set to active. The Application Handler facilitates the rule checks for each application level rule. The first function called by the Application Manager is:
This function allows the Application Manager to know if any application level rules are active and if the call should continue to the handler to check the active rules.
Rule Functions
The Application Handler is responsible for setting each application level rule to active or inactive accordingly. Only Rule Administrators may set the status of a rule.
Application Manager
Purpose
The Application Manager acts as a central hub for managing the application it is associated with.
It provides the ability to manage metadata for accounts associated with the application including:
- Roles
- Tags
- Risk Scores
- Access Levels
The Application Manager also provides the ability to check application level rules via its associated Application Handler.
Admin Roles
The Application Manager can be utilized for both updating and checking an account’s role in relation to the application. These capabilites are provided for the following roles:
Super Admin Special Case
The functions are slightly different for a Super Admin. Because there can only be one super admin at a time we use a two-step process to set a new one. The following functions are used in place of the add function the other admin types employ:
The first part of the two-step process, to propose the new Super Admin address. This can only be called by the existing Super admin.
The second part of the two-step process, confirming renounces the role for the existing Super Admin and promotes the Proposed Super Admin to the role. This can only be called by the Proposed Super Admin.
Access Levels, Risk Scores and Tags
The AppManager contains the functionality used to manage the Access Levels, Risk Scores and Tags associated with accounts, with respect to the application it manages.
Associated Contracts
The Application Manager also contains the functionality to register, deregister and check various related contracts, including:
- The Application Handler
- Protocol Compliant Tokens
- Treasury
Protocol Asset Handler Diamond Structure
Purpose
The Protocol Asset Handler Diamond serves as the access point to the protocol for a protocol supported asset. The protocol supported asset stores the Handler Diamond proxy address and uses it to call the check all rules function
. The Handler Diamond stores all asset level rule data, rule activation status, and connects the token to the App Manager for role based access control.
Asset level rules are set by Rule administrators. When setting a rule status in the Handler the protocol supplied rule id for each rule and the action type are required for the set-rule function
. The Handler Diamond stores each action type and rule together within the Rule Storage Facet.
Each Protocol supported asset type (ERC20, ERC721, etc) will need one handler diamond deployed and connected to the asset. The Handler diamond architecture will remain the same for each asset type. The asset handler diamond will consist of a proxy contract, libraries, storage facets and unique facets for that type. The unique facets for the asset type are found here:
Common Contracts
Each asset handler diamond will inherit from the following contracts:
- HandlerBase.sol
- HandlerUtils.sol
- HandlerDiamondLib.sol
- RuleStorage.sol
- StorageLib.sol
- TradingRulesFacet.sol
Protocol ERC 20
Purpose
The Protocol ERC 20 defines the base that contracts must conform to in order to be compatible with the protocol. Using the protocol ERC 20 does not restrict you from inheriting from other internal or external contracts, such as other OpenZeppelin contracts or custom logic contracts specific to your application.
Structure
The Protocol ERC 20 inherits from multiple contracts (internal and external), overrides functions from some of the inherited contracts, and defines a few functions of its own. The following contracts are inherited:
- ERC20 (external to the protocol)
- ERC165 (external to the protocol)
- EC20Burnable (external to the protocol)
- ERC20FlashMint (external to the protocol)
- ProtocolTokenCommon (internal to the protocol)
- IProtocolERX20Errors (internal to the protocol)
Protocol ERC 721
Purpose
The Protocol ERC 721 defines the base that contracts must conform to in order to be compatible with the protocol. Using the protocol ERC 721 does not restrict you from inheriting from other internal or external contracts, such as other OpenZeppelin contracts or custom logic contracts specific to your application.
Structure
The Protocol ERC 721 inherits from multiple contracts (internal and external), overrides functions from some of the inherited contracts, and defines a few functions of its own. The following contracts are inherited:
- ERC721Burnable (external to the protocol)
- ERC721URIStorage (external to the protocol)
- ERC721Enumerable (external to the protocol)
- ProtocolTokenCommon (internal to the protocol)
- AppAdministratorOrOwnerOnly (internal to the protocol)
Protocol Rule Processor Diamond
Purpose
The Rule Processor Diamond is a proxy contract that is used by application contracts to assess economic actions against rules that are active within that handler. The Rule Processor will delegate those calls to the appropriate facet contract, allowing for efficient on chain rule assessments per transaction. The Rule Processor Diamond proxy also acts as a single source address for the creation of rules for application contracts. Rule administrators of an application are allowed to add rules to the Rule Processor storage. These rules are immutable once created and can be shared across different applications with the rule id number generated by the protocol.
The Rule Processor diamond architecture consists of the Rule Processor Diamond, Rule Processor Diamond Libraries and supporting Rule Processor Facets. The library contracts store supporting functions for upgrading the diamond, connecting new facets, validating rule existence and supporting rule checks. The facet contracts fall into two categories: Storage Contracts and Processor Contracts. Storage contracts contain the data associated with a specific rule and the corresponding Processor contract contains the functions used to validate transactions relevant to the rule on chain.
see diamond diagram
Protocol Rule Processor Diamond Facets
Purpose
The Rule Processor Diamond Facets are where rule adding and rule check functions are stored in the protocol. Storage facets store the add rule functions for each rule type. Processor facets store the rule check functions and are called by an application’s handler contracts. Facets can be added or removed by the diamond to allow for upgrades to functionality of the diamond. Application contracts never call the facets directly and will only ever interact with the Rule Processor Proxy.
see facet list
Protocol Rule Processor Diamond Libraries
Purpose
The Rule Processor Diamond Libraries store functions used by the diamond structure. The Rule Processor Diamond Lib contract holds the function to store rules when added to the protocol, the diamond cut function for upgrading the diamond and adding or removing functions from facets. The Rule Processor Common Lib holds functions used throughout the facets to validate rules and parameters passed to the rule check functions.
Using these singular library contracts prevents data storage collision as functionality is added or removed from the protocol. Facets should always be removed through the diamond cut function in Rule Processor Diamond Lib.This prevents a situation where a facet may have been self destructed but the function selectors are still stored in the diamond. This could result in a “false posistive” successful transaction when attempting to add a rule and the rule is never added to the diamond. Protocol Rule Processor facets are never written with self destruct functionality.
Protocol Fee Structure
Purpose
This section outlines the overall fee structure of the protocol, how fees are applied and at what level the fees are applied. Each fee type has its own documentation associated to the specifics of that Fee type and are stored in the fee guide.
A Fee data contract is deployed at the same time as the token handler. All supporting fee data is stored in this contract and owned by the handler. Data contracts can be migrated to a new handler in the event of an upgrade so that fee data is not lost. Only the previous handler owner or app administrators can migrate the data contracts. Migrations to a new handler are completed through a two step migration process.
Fees are applied to accounts via general tags in the AppMananger. Each Fee applied via tags to an account can be additive (increase the fee amount owed) or subtractive (reduce the fee amount owed) and are expressed in basis points.
Protocol supported tokens will always assess all fees asigned to the account executing the current function. If a token has fees active and an account is tagged with applicable fees or a blank tag is used to assign a default fee, those fees are assessed on token transfers (additive). Token fees are assessed and taken from the token itself, not a collateralized token, when fees are active in the token handler.
Applies To:
- ERC20
- ERC721
Scope
Token Fees:
Token Fees work at the token level. Fees must be activated and configured for each token in the corresponding token handler. Token fees are assessed in the transfer function of the token.
Fees Evaluation
Token Evaluation:
The token determines if the handler has fees active.
The token retrieves all applicable fees for the account transferring tokens (msg.sender).
The token loops through each applicable fee and sends that amount from the transfer total to the feeCollectorAccount
for that fee. The total amount of fees assesed is tracked within the transfer as fees
, upon completion of the loop the amount of tokens minus the fees
is transferred to the recipient of the transaction.
see an example UtilApplicationERC20 -> transfer
Evaluation Exceptions
- There are no evaluation exceptions when fees are active. Fees are assessed in the token transfer function for token fees. No exceptions are made for the assessment of fees. If an address or account should not have fees assessed, there should not be a tag applied to it.
Transfer Fee
Purpose
The purpose of the Transfer Fee is to assess fees when protocol supported tokens are transferred. Application developers can utilize fees to influence their application’s economy through incentivizing behavior. Fees are assigned via tags applied to accounts through the App Manager. A blank tag may be used when adding a fee to apply to all accounts as a “default” fee. Token fees are added and activated through the token handler contract. Token transfer fees are assessed and taken from the token when fees are active in the token handler.
Application of Transfer Fee
Transfer fees are assessed in the transfer function of the token. The fees are additive. If a user has multiple applicable fees, the sum of all their fees will be assessed.
See Protocol Fee Structure
Dependencies
- Tags: This rule relies on accounts having tags registered in their AppManager, and they should match at least one of the tags in the rule for it to have any effect.
Oracle Configuration
Purpose
The purpose of oracles within the protocol is to give applications flexibility in creating, maintaining, and using user address approval and denial lists.
Oracles may be used with the protocol to provide approved and/or denied lists. These oracles are used in conjunction with the Account Approve Deny Oracle Rule which is created and applied like any other rule. Up to ten oracle rules may be applied per action. The protocol provides example oracle contracts(OracleApproved, OracleDenied) or external oracles can be created to suit the use case.
Approval List Oracle
OracleApproved allows for addresses to be added/removed from an internal list that is then checked by the Account Approve Deny Oracle Rule during the applied user action when the rule’s _oracleType is 1.
Denial List Oracle
OracleDenied allows for addresses to be added/removed from an internal list that is then checked by the Account Approve Deny Oracle Rule during the applied user action when the rule’s oracleType is 0.
External Oracle
External oracles may be used in the protocol. They must conform to the IOracle interface. Oracles must be of approved or denied style where the user address is checked for approval or denial.
NOTE: It is not necessary to implement the IOracle interface in the external oracle. The correct function needs only to exist within it.
Sample implementations for each of the above functions can be found in the example oracle contracts.
see OracleApproved
see OracleDenied
Process for using an external oracle
External oracles are used in conjunction with Account Approve Deny Oracle Rule. The external oracle is created and its deployed address is noted. When the Account Approve Deny Oracle Rule is created, this address is used in the _oracleAddress parameter whereas _oracleType corresponds to approved or denied type(0 for denied, 1 for approved).
see Account Approve Deny Oracle Rule
Oracle Rules
Oracle Rules | Purpose |
---|---|
Oracle | The purpose of the account-approve-deny-oracle rule is to check if an address in the transaction is an approved or denied address. Addresses are added to the oracle lists by the owner of the oracle contract for any reason that the owner deems necessary. Oracle rules are applied per action type and for burn and sell actions the sender address is checked. For all other actions, the receiver address is checked. If an address is not on an approved oracle list, they will be denied from receiving application tokens. This rule can be used to restrict transfers to only specific contract addresses or wallets that are approved by the oracle owner. An example is NFT exchanges that support ERC2981 royalty payments. The deny list is designed as a tool to reduce the risk of malicious actors in the ecosystem. If an address is on the deny oracle list they are denied receiving tokens. Any address not on the deny list will pass this rule check. |
Pricing Contracts
Purpose
The purpose of the pricing contracts is to serve as token-price data sources for ecosystem applications. The token pricers can be found in 2 different categories:
Developers may choose to adapt their preferred third-party solution for token pricing:
Configured pricing modules are required for the following rules:
- Account Max Value by Risk.
- Account Max Value by Access Level.
- Account Max Transaction Value by Risk Score.
Price Format
- Price is in wei of US Dollars: 1 dollar is represented by 1 _ 10^18, and 1 cent is represented 1 _ 10^16 in these contracts. This is done to have precision over the price, and to account for the possibility of tokens with extremely low prices.
- The price is given for a whole token:
- For Fungible Tokens: just like regular market data outlets, the price will be given for a whole token without decimals. e.g 1 ETH.
- For Non-Fungible-Tokens: even in the case of a fractionalized NFT, the price is still given for the whole token and not for its fractions.
Examples:
ERC20s
Let’s say we have the ERC20 called Frankenstein which has 18 decimals (1 Frankenstein = 1 * 10^18 wei of a Frankenstein). Let’s imagine that each Frankenstein is worth exactly $0.55 US Dollars (55 ¢). In this case, the price for the token will be 55 * 10^16.
ERC721s
Let’s say we have the NFT collection called FrankensteinNFT. Let’s imagine that the FrankensteinNFT with Id 222 is worth exactly $500.00 US Dollars. In this case, the price for the token will be 500 * 10^18.
Third-Party Solutions
Developers may choose their preferred third-party solutions for token pricing. In order for these to be able to communicate properly with the protocol, they simply have to deploy an adapter contract -if necessary- that implements the protocol interface for the respective kind of token.
This way, the adapter contract will live in the middle of the application and the external pricer contract in order to format the request and response between these two elements:
Developers may choose to implement and deploy an adapter contract per token standard (one for ERC20 and another for ERC721), or to implement both in a single contract to deploy. No matter the route taken, the appManager Handler must have both pricer addresses set in order to properly work.