Syntax for Defining a Policy

Policies are submitted to the Rules Engine SDK in the form of a JSON string, with the following available properties:

  • Policy: a string representing the name of the policy
  • ForeignCalls (optional): an array of Foreign Call JSON objects (defined below)
  • Trackers (optional): an array of Tracker JSON objects (defined below)
  • RulesJSON: an array of Rule JSON objects (defined below)

If you are not yet familiar with the concepts of Foreign Calls, Trackers, and Rules, check out our Concepts docs.

Here is a starting example:

{
  "Policy": "Alice's Policy",
  "ForeignCalls": [
    {
      "name": "GetAccessLevel(x)",
      "address": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e",
      "signature": "accessLevel(address)",
      "returnType": "uint256",
      "parameterTypes": "address",
      "encodedIndices": "0"
    }
  ],
  "Trackers": [
    {
      "name": "largeTransactionCount",
      "type": "uint256",
      "defaultValue": 0
    }
  ],
  "RulesJSON": [
    {
      "condition": "(receiverBalance + amount > 100) AND (FC:GetAccessLevel(x) < 1)",
      "positiveEffects": [
        "revert(\"Recipient Access Level too low to increase balance above 100\")"
      ],
      "negativeEffects": [],
      "functionSignature": "transfer(address recipient, uint256 amount)",
      "encodedValues": "address recipient, uint256 amount, uint256 receiverBalance"
    },
    {
      "condition": "amount > 10000",
      "positiveEffects": ["emit Whale alert", "TRU:largeTransactionCount += 1"],
      "negativeEffects": [],
      "functionSignature": "transfer(address recipient, uint256 amount)",
      "encodedValues": "address recipient, uint256 amount, uint256 receiverBalance"
    }
  ]
}

Foreign Calls

The ForeignCalls tag contains an array of Foreign Call JSON objects. These represent all of the Foreign Calls that will be available within the policy. Each object consists of the following properties:

  • name: The name of the foreign call (this is how it will be referred to in Rule condition and effect expressions). This must include parentheses at the end, with at least one character in between the parentheses (the characters between the parentheses have no significance, this is a bit of a design bug we will remove soon).
  • address: The address of the foreign contract being called
  • signature: The function signature of the function that will be called on the foreign contract
  • returnType: The return type of the function (currently supported types include: uint256, address, bytes and string)
  • parameterTypes: A comma delimited list of the types of each of the parameters to the function (currently supported types include: uint256, address, bytes and string)
  • encodedIndices: Currently, arbitrary data cannot be passed into the foreign call parameters. Only the data incoming from the Calling Contract can be used (the encodedValues in the Rule). This encodedIndices is a mapping of the encodedValues that will be utilized in the parameters to the foreign call. For foreign calls with more than one parameter the encodedIndicies is represented as a comma separated list of indices, for example 0, 1, 2.
    • For example, imagine the Rule’s encodedValues are address to, uint256 value. You want to pass the address as the foreign call parameter, so you would set "encodedIndices": "0"

Trackers

The Trackers tag contains an array of Tracker JSON objects. These represent all of the Trackers that will be available within the policy. Each object consists of the following properties:

  • name: The name of the tracker (this is how it will be referred to in Rule condition and effect expressions)
  • type: The type of the tracker (currently supported types include: uint256, address, bytes and string)
  • defaultValue: The initial value of the tracker

Rules

Now’s the fun part! The RulesJSON property contains an array of rule definition objects. These represent all of the rules that are configured for the policy. Each object consists of the following properties:

  • condition: a string representing a conditional expression for the rule which must evaluate to a boolean (true or false) result. Specifics defined below
  • positiveEffects: an array of effect expressions to be executed if the condition evaluates to TRUE. Specifics defined below.
  • negativeEffects: an array of effect expressions to be executed if the condition evaluates to FALSE. Specifics defined below.
  • functionSignature: a string representation of the function signature this rule will be attached to, for example transfer(address recipient, uint256 amount)
  • encodedValues: a list of values that will be encoded along with the call to the rules engine from the calling contract, for example address recipient, uint256 amount, uint256 receiverBalance

Condition

Conditional expressions must evaluate to a boolean result. A conditional expression consists of values combined using operators, for example (receiverBalance + amount < 100) AND (FC:GetAccessLevel(x) > 1).

Values available to use in conditional expressions:

  • Static values of the following types: uint256, address, bytes (0x format), string
  • Referenced values:
    • values encoded by the calling function. Referenced by the name listed in the encodedValues section for the rule.
    • Trackers: trackers are referenced in the condition statement by TR: followed by the name of the tracker (as defined in the tracker JSON object in the policy), for example TR:largeTransactionCount
    • Result of a Foreign Call: foreign calls are referenced in the condition statement by FC: followed by the name of the foreign call (as defined in the foreign call JSON object in the policy), for example FC:GetAccessLevel(x)

In the current version, strings are not character-delimited when using the SDK. Therefore, do not use special characters in strings like AND and OR or you will have unexpected behavior. If you use them in lowercase, there will be no problem.

Operators available to use in conditional expressions:

  • +: Add two values. Static values or references (must be uint values)
  • -: Subtract two values. Static values or references (must be uint values)
  • *: Multiply two values. Static values or references (must be uint values)
  • /: divide two values. Static values or references (must be uint values)
  • <: perform a less than comparison of two values. Static values or references (must be uint values)
  • >: perform a greater than comparison of two values. Static values or references (must be uint values)
  • <=: perform a less than or equal to comparison of two values. Static values or references (must be uint values)
  • >=: perform a greater than or equal to comparison of two values. Static values or references (must be uint values)
  • ==: perform an equal to comparison of two values. Static values or references (all types supported)
  • AND: perform an AND operation on two expressions.
  • OR: perform an OR operation on two expressions.
  • ( and ) : parenthesis allow for multiple “combined expressions” (described in detail in the NOTE below)

Order of operations is not assumed, it must be explicit. Any combination of more than 2 boolean expressions must be grouped using parentheses. For example:

  • Not Supported: 1 == 1 AND 2 == 2 OR 3 == 4
  • Supported: 1 == 1 AND (2 == 2 OR 3 == 4)

Effects (positiveEffects and negativeEffects)

Effects under the positiveEffects tag are triggered if the rule Condition evaluates to TRUE. Effects under the negativeEffects tag are triggered if the rule Condition evaluates to FALSE. Effects can be one of four types: revert, event, tracker update, foreign call.

Revert Effects

A revert effect will revert the transaction when triggered. The syntax is: revert(“Revert message”), where the message is optional, so could also simply use revert.

Event Effects

A event effect will emit an event when triggered. The syntax is: emit <Event message>, for example emit Whale alert!.

Tracker Update Effects

A tracker update effect will update a tracker’s value when triggered. The syntax is TRU:<trackerName> <updateOperator> <expression>, for example TRU:largeTransactionCount += 1.

The name of the tracker is the name defined in the Trackers tag.

One of the following update operators must be used:

  • +=: add the value following the operator to the current value of the tracker (both tracker and value must be uint256 types)
  • -=: subtract the value following the operator from the current value of the tracker (both tracker and value must be uint256 types)
  • *=: multiply the value following the operator by the current value of the tracker (both tracker and value must be uint256 types)
  • /=: divide the tracker by the value following the operator (both tracker and value must be uint256 types)
  • =: assign the value of the tracker to the value after the operator (all types supported, uint256, address, bytes, string)

For the expression, this follows the same syntax as conditional expressions. Except, you will not use any relational or boolean operators (<, >, ==, AND, OR) since trackers cannot be booleans.

Foreign Call Effects

A foreign call effect will call a foreign contract when triggered. The syntax is the same as when using a foreign call to get a value for an expression, e.g. FC:UpdateVIPStatus(x).

Example Policies

Here are three more examples to help solidify the concepts.

Simple rule

Rule: Allow transfers if there is a minimum balance of 500 in the receiving wallet and don’t allow transfers if the amount being sent is more than 1000.

{
  "Policy": "Transfer Constraints",
  "ForeignCalls": [],
  "Trackers": [],
  "RulesJSON": [
    {
      "condition": "(receiverBalance + amount >= 500) AND (amount <= 1000)",
      "positiveEffects": [],
      "negativeEffects": ["revert(\"Transfer violates balance or limit constraints\")"],
      "functionSignature": "transfer(address recipient, uint256 amount)",
      "encodedValues": "address recipient, uint256 amount, uint256 receiverBalance"
    }
  ]
}

Rule using a Foreign Call

Rule: Block transfers over 50,000 unless the sender is a VIP.

{
  "Policy": "VIP Transfer Restriction",
  "ForeignCalls": [
    {
      "name": "isVIP(x)",
      "address": "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5",
      "signature": "isVIP(address)",
      "returnType": "uint256",
      "parameterTypes": "address",
      "encodedIndices": "0"
    }
  ],
  "Trackers": [],
  "RulesJSON": [
    {
      "condition": "(amount > 50000) AND (FC:isVIP(x) == 0)",
      "positiveEffects": ["revert(\"Sender must be VIP to transfer over 50,000\")"],
      "negativeEffects": [],
      "functionSignature": "transfer(address recipient, uint256 amount)",
      "encodedValues": "address recipient, uint256 amount, address sender"
    }
  ]
}

Rule using a Tracker

Rule: Only allow 5 mints per day.

{
  "Policy": "Daily Mint Limit",
  "ForeignCalls": [],
  "Trackers": [
    {
      "name": "dailyMintCount",
      "type": "uint256",
      "defaultValue": 0
    }
  ],
  "RulesJSON": [
    {
      "condition": "TR:dailyMintCount < 5",
      "positiveEffects": ["TRU:dailyMintCount += 1"],
      "negativeEffects": ["revert(\"Daily mint limit reached\")"],
      "functionSignature": "mint(uint256 amount)",
      "encodedValues": "uint256 amount"
    }
  ]
}