Writing a Smart Contract Policy File
Introduction
A smart contract policy file is a .txt file uploaded from the desktop machine to the MPC backend via the policy GUI.
Once uploaded, the file is used as an element of an MPC policy, and only governs the actions of users included as initiators within that MPC policy.
Included Information
Policy files must include information that answers specific questions regarding which actions are permitted. This all boils down to which actions are allowed on which smart contract.
Which Contract?
The file specifies a smart contract by chain ID and address.
Once a file specifying a given smart contract is uploaded to the MPC, all transactions with that contract are FORBIDDEN except those functions explicitly listed in a policy file.
Which Function?
The policy file specifies one or more functions that users can call. If a function signature is listed without any parameters, users can call any parameter they please for that function.
Which Parameter?
If parameters are listed for a function, users can only complete function calls using those parameters listed.
If no parameters are listed for a function, users will be able to run the function with any parameters they want.
- in means the value must only be one of the exact amounts listed. amount in ["1","2","3","4"] means it MUST be 1, 2, 3, or 4. Nothing else will work. in can also work on groups of values. in[[“abc”,”1”],[“def”,”3”],[“ghi”,”4”]] passes ["abc","1"] as acceptable, but ["abc","3"] or ["1","abc"] shall not pass; even though both "abc" and "3" are listed, they are not listed together as a valid pairing. You can nest an array using in to indicate that the ONLY allowable values are that entire array in that order. For example, in [["1","2","3"]] means that only ["1","2","3"] will pass. Multiple arrays can be included, i.e. in [["1","2","3"],["4","5","6"]].
- inRange means the value must fall between the numbers listed. inRange includes the numbers defining the range. inRange ["1","5"] will pass 1, 2, 3, 4, or 5 as acceptable values.
- containedIn means the value(s) must be included in the associated list/array. You can send more than one value, you can use a value more than once, and/or choose not to include some of the allowed values in what you send. containedIn [“0x111”,”0x222”,”0x333”] will pass if we send ["0x222","0x111"] or just ["0x333"]; it fails if we try to send any value not included in the list, such as ["0x123"].
- inOrderDoesntMatter means every listed parameter must be used, and only once, but the order they are listed does not matter. inOrderDoesntMatter [“0x111”,”0x222”] will pass ["0x111","0x222"] or ["0x222","0x111"]; no other values can pass.
Format
Smart contract policy files are case-sensitive. Ensure any information you input into the policy file matches the cases used in the on-chain smart contract. Smart contracts are written using Solidity, and the data they manipulate (and the way that data is manipulated) is restricted to certain data types. The types of data Solidity works with can be found at https://docs.soliditylang.org/en/v0.8.27/types.html.
Solidity operates via functions and parameters. A smart contract policy file controls what functions a user can utilize in a given contract.
Not every contract will use all data types. Check the contract on a chain explorer to make sure the data types used by the contract and the data types you are writing policy for are aligned.
Let's look at an example of a smart contract policy:
Kinds of Data
The greater-than-sign ">" indicates the kind of data being described:
- One is used to indicate the chain: > 11155111
- Two are used for the contract's address on the chain: >> 0x213214324324234324324234
- Three designate a function signature: >>> mint(uint amount,address to) These signatures are not arbitrary names. They must align with function signatures used by the smart contract.
- Four are used for parameter validation: >>>> amountAndTo[0] inRange [“1”,”300”]These parameters are not arbitrary. They must align with parameters used by the smart contract. Parameters must be listed as elements of an array within square brackets.
Each line has a single space between the greater-than-signs and the values. For parameter validation, parameters are presented in an array; these arrays can nest arrays in circumstances where you present a variety of arrays as valid parameters.
Tuples
To explain tuples, let's look at an example of minting. Whenever the user mints with this contract, we want to make sure they mint a positive amount (uint, unsigned integer) and indicate where to send it to (address).
Functions that must always be paired together in a particular order are called tuples. Tuples are shown by two parameters within parenthesis, separated by a comma. In the case of the minting example above, the tuple would look like this: (uint,address)
Tuples need to be named. These tuple names are not arbitrary names. They must align with the tuple names used by the smart contract. For our example, the tuple is called amountAndTo.
In the policy file, the tuple name immediately follows the description of the tuple, and the description and tuple are, together, enclosed in another set of parentheses. A space separates the tupple description and tuple name. The final tuple would look like this: ((uint,address) amountAndTo)
Validators
The parameters listed for a function signature can be described different ways. You can limit the user to only use the values listed, list values to describe a range, and more. You do this by writing a validator term between the parameter name and the parameter values. Smart contract policy files can use four different validators: in, inRange, containedIn, and inOrderDoesntMatter
- in means the value must only be one of the exact amounts listed. amount in ["1","2","3","4"] means it MUST be 1, 2, 3, or 4. Nothing else will work. in can also work on groups of values. in[[“abc”,”1”],[“def”,”3”],[“ghi”,”4”]] passes ["abc","1"] as acceptable, but ["abc","3"] or ["1","abc"] shall not pass; even though both "abc" and "3" are listed, they are not listed together as a valid pairing. You can nest an array using in to indicate that the ONLY allowable values are that entire array in that order. For example, in [["1","2","3"]] means that only ["1","2","3"] will pass. Multiple arrays can be included, i.e. in [["1","2","3"],["4","5","6"]].
- inRange means the value must fall between the numbers listed. inRange includes the numbers defining the range. inRange ["1","5"] will pass 1, 2, 3, 4, or 5 as acceptable values.
- containedIn means the value(s) must be included in the associated list/array. You can send more than one value, you can use a value more than once, and/or choose not to include some of the allowed values in what you send. containedIn [“0x111”,”0x222”,”0x333”] will pass if we send ["0x222","0x111"] or just ["0x333"]; it fails if we try to send any value not included in the list, such as ["0x123"].
- inOrderDoesntMatter means every listed parameter must be used, and only once, but the order they are listed does not matter. inOrderDoesntMatter [“0x111”,”0x222”] will pass ["0x111","0x222"] or ["0x222","0x111"]; no other values can pass.
Sticking with the example we used for tuples previously, let's limit amountAndTo to any amount from 500 to 1000 and ensure the user can only send the tokens to one of four addresses:
Input the function, tuple description, and tuple name:
>>> mint ((uint,address) amountAndTo)
Input the parameter limits, one parameter per line. For tuples, every parameter included needs its own limits. Indicate which parameter is being limited by its tuple name followed by the parameter's order in the tuple. Start the count of tuple parameters from zero. First, limit the minting activity to a specific range using the inRange validator:
>>>> amountAndTo[0] inRange ["500","1000"]
Next, limit the destination of the tokens to any one of four specific addresses using the in validator:
>>>> amountAndTo[1] in ["0x111","0x222","0x333","0x444"]
Double-check. For the example above, the tuple and parameters should look like this:
Policy File Requirements
A policy file MUST:
- be saved as a .txt file.
- begin by specifing one smart contract (by including the contract's chain and address.
- set policy for every function you want your users to be able to perform under the policy.
A policy file that specifies a contract but does not list any functions will prevent users governed by that policy from performing any actions with that contract.
A policy file MAY OPTIONALLY set policy for more than one contract.
A policy fine CANNOT include comments or any other content besides the propperly-formatted policy data.
Single-Contract Policy File Structure
A single-contract policy file has the following structure:
A contract can have any number of functions, and a function can have any number of parameters.
Multi-Contract Policy File Structure
A multi-contract policy file has the following structure:
A file can have any number of contracts. The functions, however, are restricted to the contract they are listed directly below. To apply identical policy settings to multiple contracts, copy the relevant lines and paste them below each contract listed.
Troubleshooting
When uploading a policy file to the MPC via the desktop interface, there are three errors you may encounter that are directly related to the policy file itself.
The system runs a series of checks on the file and stops checking as soon as the first error is encountered:
- File Type
- File Structure
- Contract Addresses
This means that if the file fails the file type check, the system will notify you about the type issue and ONLY the type issue. If the file is also poorly structured, you won't be told until after resubmitting the file as a .txt.
Invalid File Format
The system only takes .txt files. It will throw this error if you try to upload a file in a different format.
It also displays this error if you try to drag and drop a .txt file from a directory that the desktop app cannot access.
Resolution
- Ensure the file has the .txt extension
- Place the file in a folder visible in the "select file" dialog box
Invalid Smart Contract File Structure
The system displays this error if there is an error with the syntax of the file. This can be anything from an extra space to the wrong type of brackets or an open parenthesis without a partner to close the statement.
Remember, the system cannot handle extraneous content like comments.
Resolution
- Check the file carefully against the rules outlined in this article
- Show hidden characters to check for "invisible" items like spaces or empty lines at the end of the file
The syntax used in the policy files was developed in-house by Custody Infrastructure. Therefore, there are no linters available to catch the errors. Files must be checked manually.
Invalid Policy Configuration File: Some of the Addresses are not Whitelisted Contacts
The final check run by the system is to verify that the listed contracts are reachable. It checks that the chain-address combination of every contract listed belongs to an existing contact in the address book.
If no matching contact is found for a contract, the system will display this error message.
Resolution
Check your address book and add any smart contracts that aren't listed. See Adding a New Address to the Allowlist for details.
Examples
Here are a pair of real-world examples of policy files.
Asset Transfer
The below example illustrates allowing users to to transfer assets.
The address[] and _addresses parameters are listed without specifying any values for them; the user is permitted to supply any addresses they wish. _amount in ["500","600"] constrains the user to only transferring 500 or 600 of the asset.
Asset Swap
The following example illustrates allowing users to swap assets.
This policy limits the dstToken function to one of only two addresses.