Initialize a Tracker
As outlined in the policy configuration guide, you define your trackers in the policy.json
file for your project. Below is an example policy to illustrate the examples that will follow.
{
"Policy": "Tracker Example Policy",
"PolicyType": "open",
"CallingFunctions": [
{
"name": "mint(uint256 amount)",
"functionSignature": "mint(uint256 amount)",
"encodedValues": "uint256 amount"
}
],
"ForeignCalls": [],
"Trackers": [
{
"name": "TradingVolume",
"type": "uint256",
"initialValue": 0
}
],
"MappedTrackers": [],
"Rules": [
// purposefully empty for now
]
}
There are two ways that you can utilize trackers in your project.
- Trackers can be used as input values in rule expressions.
- Trackers can be updated as effects of rules.
Let’s see how to accomplish these two usage options with standard and mapped trackers
Usage in Rules
First, we need to add at least one rule to the policy that will use the named trackers.
Tracker
This rule condition ensures that the total volume so far and the new amount
does not exceed 1 billion tokens.
{
"Policy": "Tracker Example Policy",
"PolicyType": "open",
"CallingFunctions": [
{
"name": "mint(uint256 amount)",
"functionSignature": "mint(uint256 amount)",
"encodedValues": "uint256 amount"
}
],
"ForeignCalls": [],
"Trackers": [
{
"name": "TradingVolume",
"type": "uint256",
"initialValue": 0
},
{
"name": "TradeCountPerUser",
"type": "uint256",
"initialValue": 0
}
],
"MappedTrackers": [],
"Rules": [
{
"condition": "(TR:TradingVolume + amount) < 1_000_000_000 AND TR:TradeCountPerUser(from) < 10",
"positiveEffects": [],
"negativeEffects": ["revert(\"Trading Volume Max Reached\")"],
"callingFunction": "mint(uint256 amount)"
}
]
}
Mapped Tracker
This rule condition ensures that the sender has not exceeded 10 transactions.
{
"Policy": "Tracker Example Policy",
"PolicyType": "open",
"CallingFunctions": [
{
"name": "mint(uint256 amount)",
"functionSignature": "mint(uint256 amount)",
"encodedValues": "uint256 amount"
}
],
"ForeignCalls": [],
"Trackers": [],
"MappedTrackers": [
{
"name": "TradeCountPerUser",
"keyType": "address",
"valueType": "uint256",
"initialKeys": ["0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e"],
"initialValues": [0]
}
],
"Rules": [
{
"condition": "TR:TradeCountPerUser(from) < 10",
"positiveEffects": ["TRU:TradeCountPerUser(from) += 1"],
"negativeEffects": ["revert(\"Trading Transaction Max Reached\")"],
"callingFunction": "mint(uint256 amount)"
}
]
}
Note that this rule is applied to the internal _update
function. This ensures the policy is
applied to both the transfer
and transferFrom
function calls.
Tracker Update Effects
As of now, this rule will always succeed because the tracker value is never updated. Below we add a positiveEffect
that updates the tracker value whenever a transfer
call is successful.
Tracker
//...
"Rules": [
{
"condition": "(TR:TradingVolume + amount) < 1_000_000_000",
"positiveEffects": [
"TRU:TradingVolume += amount"
],
"negativeEffects": ["revert(\"Trading Volume Max Reached\")"],
"callingFunction": "mint(uint256 amount)"
}
]
//...
Mapped Tracker
//...
"Rules": [
{
"condition": "TR:TradeCountPerUser(from) < 10",
"positiveEffects": [
"TRU:TradeCountPerUser(from) += 1"
],
"negativeEffects": ["revert(\"Trading Transaction Max Reached\")"],
"callingFunction": "mint(uint256 amount)"
}
]
//...
Notice that when referencing a tracker value in a rule condition or as a read value in an effect, you precede the name with TR
as in TR:TradingVolume
.When referencing in an update you must precede it with TRU
as in
TRU:TradingVolume
Taking it Further
The rule defined thus far is a good start, but needs additional configuration to achieve the desired outcome.
First, as soon as 1_000_000_000
in trading volume is reached the token will no longer be transferrable. Fixing this will requires resetting the TradingVolume
tracker when the defined duration is reached. To do this we need another tracker to record the timestamp and mark the beginning of a rolling 24 hour period. Then we test for that condition in a new rule and reset the tracker values when the condition is true.
{
"Policy": "Tracker Example Policy",
"PolicyType": "open",
"CallingFunctions": [
{
"name": "mint(uint256 amount)",
"functionSignature": "mint(uint256 amount)",
"encodedValues": "uint256 amount"
}
],
"ForeignCalls": [],
"Trackers": [
{
"name": "TimeStamp",
"type": "uint256",
"initialValue": 0
},
{
"name": "TradingVolume",
"type": "uint256",
"initialValue": 0
}
],
"MappedTrackers": [
{
"name": "UserTimeStamp",
"keyType": "address",
"valueType": "uint256",
"initialKeys": ["0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e"],
"initialValues": [0]
},
{
"name": "TradeCountPerUser",
"keyType": "address",
"valueType": "uint256",
"initialKeys": ["0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e"],
"initialValues": [0]
}
],
"Rules": [
{
"condition": "(GV:BLOCK_TIMESTAMP - TR:TimeStamp) >= 86400",
"positiveEffects": ["TRU:TimeStamp = GV:BLOCK_TIMESTAMP", "TRU:TradingVolume = 0"],
"negativeEffects": [],
"callingFunction": "mint(uint256 amount)"
},
{
"condition": "(TR:TradingVolume + amount) < 1_000_000_000",
"positiveEffects": ["TRU:TradingVolume += amount"],
"negativeEffects": ["revert(\"Trading Volume Max Reached\")"],
"callingFunction": "mint(uint256 amount)"
},
{
"condition": "(GV:BLOCK_TIMESTAMP - TR:UserTimeStamp(from)) >= 86400",
"positiveEffects": [
"TRU:UserTimeStamp(from) = GV:BLOCK_TIMESTAMP",
"TRU:TradeCountPerUser = 0"
],
"negativeEffects": [],
"callingFunction": "mint(uint256 amount)"
},
{
"condition": "TR:TradeCountPerUser(from) < 10",
"positiveEffects": ["TRU:TradeCountPerUser(from) += 1"],
"negativeEffects": ["revert(\"Trading Transaction Max Reached\")"],
"callingFunction": "mint(uint256 amount)"
}
]
}
You may have noticed the GV:BLOCK_TIMESTAMP
variable above. The Rules Engine enables you to
access the block level details with the GV
prefix.