@chainlink/ace 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.foundry-version +1 -0
- package/.github/CODEOWNERS +1 -0
- package/.github/workflows/auto-release-version.yml +107 -0
- package/.github/workflows/create-version-pr.yml +95 -0
- package/.github/workflows/forge-docs.yml +90 -0
- package/.github/workflows/forge-test.yml +59 -0
- package/.solhint-test.json +18 -0
- package/.solhint.json +16 -0
- package/.solhintignore +3 -0
- package/.solhintignore-test +2 -0
- package/Glossary.md +141 -0
- package/LICENSE +59 -0
- package/README.md +218 -0
- package/assets/chainlink-logo.svg +21 -0
- package/chainlink-ace-License-grants +2 -0
- package/foundry.toml +33 -0
- package/getting_started/GETTING_STARTED.md +477 -0
- package/getting_started/MyVault.sol +48 -0
- package/getting_started/advanced/.env.example +36 -0
- package/getting_started/advanced/GETTING_STARTED_ADVANCED.md +431 -0
- package/getting_started/advanced/SanctionsList.sol +25 -0
- package/getting_started/advanced/SanctionsPolicy.sol +58 -0
- package/package.json +41 -0
- package/packages/cross-chain-identity/README.md +148 -0
- package/packages/cross-chain-identity/docs/API_GUIDE.md +120 -0
- package/packages/cross-chain-identity/docs/API_REFERENCE.md +271 -0
- package/packages/cross-chain-identity/docs/CONCEPTS.md +253 -0
- package/packages/cross-chain-identity/docs/CREDENTIAL_FLOW.md +195 -0
- package/packages/cross-chain-identity/docs/SECURITY.md +70 -0
- package/packages/cross-chain-identity/src/CredentialRegistry.sol +245 -0
- package/packages/cross-chain-identity/src/CredentialRegistryIdentityValidator.sol +339 -0
- package/packages/cross-chain-identity/src/CredentialRegistryIdentityValidatorPolicy.sol +71 -0
- package/packages/cross-chain-identity/src/IdentityRegistry.sol +123 -0
- package/packages/cross-chain-identity/src/TrustedIssuerRegistry.sol +140 -0
- package/packages/cross-chain-identity/src/interfaces/ICredentialDataValidator.sol +30 -0
- package/packages/cross-chain-identity/src/interfaces/ICredentialRegistry.sol +170 -0
- package/packages/cross-chain-identity/src/interfaces/ICredentialRequirements.sol +192 -0
- package/packages/cross-chain-identity/src/interfaces/ICredentialValidator.sol +37 -0
- package/packages/cross-chain-identity/src/interfaces/IIdentityRegistry.sol +85 -0
- package/packages/cross-chain-identity/src/interfaces/IIdentityValidator.sol +18 -0
- package/packages/cross-chain-identity/src/interfaces/ITrustedIssuerRegistry.sol +61 -0
- package/packages/cross-chain-identity/test/CredentialRegistry.t.sol +220 -0
- package/packages/cross-chain-identity/test/CredentialRegistryIdentityValidator.t.sol +554 -0
- package/packages/cross-chain-identity/test/CredentialRegistryIdentityValidatorPolicy.t.sol +114 -0
- package/packages/cross-chain-identity/test/IdentityRegistry.t.sol +106 -0
- package/packages/cross-chain-identity/test/IdentityValidator.t.sol +969 -0
- package/packages/cross-chain-identity/test/TrustedIssuerRegistry.t.sol +123 -0
- package/packages/cross-chain-identity/test/helpers/BaseProxyTest.sol +112 -0
- package/packages/cross-chain-identity/test/helpers/MockCredentialDataValidator.sol +26 -0
- package/packages/cross-chain-identity/test/helpers/MockCredentialRegistryReverting.sol +131 -0
- package/packages/policy-management/README.md +197 -0
- package/packages/policy-management/docs/API_GUIDE.md +290 -0
- package/packages/policy-management/docs/API_REFERENCE.md +173 -0
- package/packages/policy-management/docs/CONCEPTS.md +156 -0
- package/packages/policy-management/docs/CUSTOM_POLICIES_TUTORIAL.md +195 -0
- package/packages/policy-management/docs/POLICY_ORDERING_GUIDE.md +91 -0
- package/packages/policy-management/docs/SECURITY.md +57 -0
- package/packages/policy-management/src/core/Policy.sol +124 -0
- package/packages/policy-management/src/core/PolicyEngine.sol +382 -0
- package/packages/policy-management/src/core/PolicyFactory.sol +92 -0
- package/packages/policy-management/src/core/PolicyProtected.sol +126 -0
- package/packages/policy-management/src/extractors/ComplianceTokenForceTransferExtractor.sol +57 -0
- package/packages/policy-management/src/extractors/ComplianceTokenFreezeUnfreezeExtractor.sol +54 -0
- package/packages/policy-management/src/extractors/ComplianceTokenMintBurnExtractor.sol +61 -0
- package/packages/policy-management/src/extractors/ERC20ApproveExtractor.sol +57 -0
- package/packages/policy-management/src/extractors/ERC20TransferExtractor.sol +62 -0
- package/packages/policy-management/src/extractors/ERC3643ForcedTransferExtractor.sol +56 -0
- package/packages/policy-management/src/extractors/ERC3643FreezeUnfreezeExtractor.sol +55 -0
- package/packages/policy-management/src/extractors/ERC3643MintBurnExtractor.sol +51 -0
- package/packages/policy-management/src/extractors/ERC3643SetAddressFrozenExtractor.sol +51 -0
- package/packages/policy-management/src/interfaces/IExtractor.sol +17 -0
- package/packages/policy-management/src/interfaces/IMapper.sol +17 -0
- package/packages/policy-management/src/interfaces/IPolicy.sol +61 -0
- package/packages/policy-management/src/interfaces/IPolicyEngine.sol +264 -0
- package/packages/policy-management/src/interfaces/IPolicyProtected.sol +48 -0
- package/packages/policy-management/src/policies/AllowPolicy.sol +104 -0
- package/packages/policy-management/src/policies/BypassPolicy.sol +90 -0
- package/packages/policy-management/src/policies/IntervalPolicy.sol +223 -0
- package/packages/policy-management/src/policies/MaxPolicy.sol +73 -0
- package/packages/policy-management/src/policies/OnlyAuthorizedSenderPolicy.sol +84 -0
- package/packages/policy-management/src/policies/OnlyOwnerPolicy.sol +35 -0
- package/packages/policy-management/src/policies/PausePolicy.sol +82 -0
- package/packages/policy-management/src/policies/README.md +632 -0
- package/packages/policy-management/src/policies/RejectPolicy.sol +89 -0
- package/packages/policy-management/src/policies/RoleBasedAccessControlPolicy.sol +162 -0
- package/packages/policy-management/src/policies/SecureMintPolicy.sol +271 -0
- package/packages/policy-management/src/policies/VolumePolicy.sol +133 -0
- package/packages/policy-management/src/policies/VolumeRatePolicy.sol +192 -0
- package/packages/policy-management/test/PolicyEngine.t.sol +368 -0
- package/packages/policy-management/test/PolicyFactory.t.sol +114 -0
- package/packages/policy-management/test/PolicyProtectedToken.t.sol +75 -0
- package/packages/policy-management/test/extractors/ComplianceTokenForceTransferExtractor.t.sol +59 -0
- package/packages/policy-management/test/extractors/ComplianceTokenFreezeUnfreezeExtractor.t.sol +74 -0
- package/packages/policy-management/test/extractors/ComplianceTokenMintBurnExtractor.t.sol +92 -0
- package/packages/policy-management/test/extractors/ERC20ApproveExtractor.t.sol +58 -0
- package/packages/policy-management/test/extractors/ERC3643ForcedTransferExtractor.t.sol +59 -0
- package/packages/policy-management/test/extractors/ERC3643FreezeUnfreezeExtractor.t.sol +74 -0
- package/packages/policy-management/test/extractors/ERC3643MintBurnExtractor.t.sol +73 -0
- package/packages/policy-management/test/extractors/ERC3643SetAddressFrozenExtractor.t.sol +56 -0
- package/packages/policy-management/test/helpers/BaseProxyTest.sol +75 -0
- package/packages/policy-management/test/helpers/CustomMapper.sol +26 -0
- package/packages/policy-management/test/helpers/DummyExtractor.sol +11 -0
- package/packages/policy-management/test/helpers/ExpectedParameterPolicy.sol +39 -0
- package/packages/policy-management/test/helpers/MockAggregatorV3.sol +51 -0
- package/packages/policy-management/test/helpers/MockToken.sol +66 -0
- package/packages/policy-management/test/helpers/MockTokenExtractor.sol +34 -0
- package/packages/policy-management/test/helpers/PolicyAlwaysAllowed.sol +45 -0
- package/packages/policy-management/test/helpers/PolicyAlwaysContinue.sol +23 -0
- package/packages/policy-management/test/helpers/PolicyAlwaysRejected.sol +23 -0
- package/packages/policy-management/test/helpers/PolicyFailingRun.sol +22 -0
- package/packages/policy-management/test/policies/AllowPolicy.t.sol +174 -0
- package/packages/policy-management/test/policies/BypassPolicy.t.sol +159 -0
- package/packages/policy-management/test/policies/IntervalPolicy.t.sol +307 -0
- package/packages/policy-management/test/policies/MaxPolicy.t.sol +54 -0
- package/packages/policy-management/test/policies/OnlyAuthorizedSenderPolicy.t.sol +95 -0
- package/packages/policy-management/test/policies/OnlyOwnerPolicy.t.sol +47 -0
- package/packages/policy-management/test/policies/PausePolicy.t.sol +75 -0
- package/packages/policy-management/test/policies/RejectPolicy.t.sol +182 -0
- package/packages/policy-management/test/policies/RoleBasedAccessControlPolicy.t.sol +223 -0
- package/packages/policy-management/test/policies/SecureMintPolicy.t.sol +442 -0
- package/packages/policy-management/test/policies/VolumePolicy.t.sol +158 -0
- package/packages/policy-management/test/policies/VolumeRatePolicy.t.sol +165 -0
- package/packages/tokens/erc-20/src/ComplianceTokenERC20.sol +345 -0
- package/packages/tokens/erc-20/src/ComplianceTokenStoreERC20.sol +29 -0
- package/packages/tokens/erc-20/test/ComplianceTokenERC20.t.sol +556 -0
- package/packages/tokens/erc-20/test/helpers/BaseProxyTest.sol +75 -0
- package/packages/tokens/erc-3643/README.md +24 -0
- package/packages/tokens/erc-3643/src/ComplianceTokenERC3643.sol +564 -0
- package/packages/tokens/erc-3643/src/ComplianceTokenStoreERC3643.sol +30 -0
- package/packages/tokens/erc-3643/test/ComplianceTokenERC3643.t.sol +815 -0
- package/packages/tokens/erc-3643/test/helpers/BaseProxyTest.sol +76 -0
- package/packages/tokens/erc-3643/test/helpers/ExpectedContextPolicy.sol +32 -0
- package/packages/vendor/erc-3643/compliance/modular/IModularCompliance.sol +220 -0
- package/packages/vendor/erc-3643/registry/interface/IClaimTopicsRegistry.sol +101 -0
- package/packages/vendor/erc-3643/registry/interface/IIdentityRegistry.sol +251 -0
- package/packages/vendor/erc-3643/registry/interface/IIdentityRegistryStorage.sol +191 -0
- package/packages/vendor/erc-3643/registry/interface/ITrustedIssuersRegistry.sol +161 -0
- package/packages/vendor/erc-3643/token/IToken.sol +457 -0
- package/packages/vendor/onchain-id/interface/IClaimIssuer.sol +53 -0
- package/packages/vendor/onchain-id/interface/IERC734.sol +110 -0
- package/packages/vendor/onchain-id/interface/IERC735.sol +105 -0
- package/packages/vendor/onchain-id/interface/IIdentity.sol +26 -0
- package/packages/vendor/onchain-id/interface/IImplementationAuthority.sol +21 -0
- package/remappings.txt +6 -0
- package/script/DeployComplianceTokenERC20.s.sol +191 -0
- package/script/DeployComplianceTokenERC3643.s.sol +208 -0
- package/script/DeploySimpleComplianceToken.s.sol +38 -0
- package/script/getting_started/DeployGettingStarted.s.sol +74 -0
- package/script/getting_started/advanced/DeployAdvancedGettingStarted.s.sol +332 -0
- package/script/getting_started/advanced/DeploySanctionsList.s.sol +26 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# API Guide: Policy Management
|
|
2
|
+
|
|
3
|
+
This guide provides practical, task-oriented examples for the most common operations you'll perform with the Policy Management component.
|
|
4
|
+
|
|
5
|
+
## 1. Protecting a Contract Function
|
|
6
|
+
|
|
7
|
+
To protect a smart contract function with the policy system, you must perform two steps:
|
|
8
|
+
|
|
9
|
+
1. In your contract's code, add the `runPolicy` modifier to the function.
|
|
10
|
+
2. Connect your contract instance to a `PolicyEngine` instance.
|
|
11
|
+
|
|
12
|
+
### Step 1.1: Applying the `runPolicy` Modifier
|
|
13
|
+
|
|
14
|
+
In your contract, import `PolicyProtected`, inherit from it, and add `runPolicy` to any function you want to secure.
|
|
15
|
+
|
|
16
|
+
```solidity
|
|
17
|
+
import { PolicyProtected } from "@chainlink/policy-management/core/PolicyProtected.sol";
|
|
18
|
+
|
|
19
|
+
contract MyGuardedContract is PolicyProtected {
|
|
20
|
+
event ActionCompleted(address indexed caller);
|
|
21
|
+
|
|
22
|
+
// This function is now protected by the Policy Engine.
|
|
23
|
+
function protectedAction() public runPolicy {
|
|
24
|
+
emit ActionCompleted(msg.sender);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function unprotectedAction() public {
|
|
28
|
+
// This function is not protected and can be called freely.
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Step 1.2: Connecting the Policy Engine
|
|
34
|
+
|
|
35
|
+
After deploying your contract, you must attach it to a `PolicyEngine`. This is typically done in the contract's `initialize` function.
|
|
36
|
+
|
|
37
|
+
Here is how you would write the deployment script code:
|
|
38
|
+
|
|
39
|
+
```solidity
|
|
40
|
+
// --- In your deployment script ---
|
|
41
|
+
PolicyEngine policyEngine = new PolicyEngine();
|
|
42
|
+
bool defaultAllow = true;
|
|
43
|
+
policyEngine.initialize(defaultAllow, address(this)); // Recommended default
|
|
44
|
+
|
|
45
|
+
MyGuardedContract myContract = new MyGuardedContract();
|
|
46
|
+
|
|
47
|
+
// The initialize function calls __PolicyProtected_init to set the engine
|
|
48
|
+
myContract.initialize(OWNER_ADDRESS, address(policyEngine));
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 2. Configuring Policies for a Function
|
|
52
|
+
|
|
53
|
+
Once the engine is attached, you can add one or more policies to any protected function. A policy can either be self-contained or it can be designed to receive parameters extracted from the function call.
|
|
54
|
+
|
|
55
|
+
### Step 2.1: Adding a Policy Without Parameters
|
|
56
|
+
|
|
57
|
+
**The Goal:** You want to add a policy that does not require any data from the function it is protecting (e.g., checking the `msg.sender` against an internal access list).
|
|
58
|
+
|
|
59
|
+
**The Action:** In your deployment script, deploy the policy and use `policyEngine.addPolicy()`. The final argument, `policyParameterNames`, will be an empty array.
|
|
60
|
+
|
|
61
|
+
```solidity
|
|
62
|
+
// --- In your deployment script ---
|
|
63
|
+
|
|
64
|
+
// Deploy a policy that doesn't need parameters, like OnlyOwnerPolicy
|
|
65
|
+
OnlyOwnerPolicy onlyOwnerPolicy = new OnlyOwnerPolicy();
|
|
66
|
+
onlyOwnerPolicy.initialize(address(policyEngine), OWNER_ADDRESS, "");
|
|
67
|
+
|
|
68
|
+
// Get the function selector
|
|
69
|
+
bytes4 selector = myContract.protectedAction.selector;
|
|
70
|
+
|
|
71
|
+
// Add the policy to the engine's registry for that function
|
|
72
|
+
policyEngine.addPolicy(
|
|
73
|
+
address(myContract), // The target contract
|
|
74
|
+
selector, // The function selector
|
|
75
|
+
address(onlyOwnerPolicy), // The policy contract address
|
|
76
|
+
new bytes32[](0) // An empty array for parameter names
|
|
77
|
+
);
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Step 2.2: Adding a Policy That Requires Parameters
|
|
81
|
+
|
|
82
|
+
**The Goal:** You want to add a policy that makes a decision based on the arguments of the function call (e.g., checking the `value` of an ERC20 `transfer`).
|
|
83
|
+
|
|
84
|
+
**The Actions:** This is a two-step process in your deployment script.
|
|
85
|
+
|
|
86
|
+
1. First, you must register an `Extractor` contract that knows how to parse the parameters for that function's signature.
|
|
87
|
+
2. Second, when you call `addPolicy`, you must specify which of the extracted parameters your policy needs.
|
|
88
|
+
|
|
89
|
+
Here is how you would write the deployment script code:
|
|
90
|
+
|
|
91
|
+
```solidity
|
|
92
|
+
// --- In your deployment script ---
|
|
93
|
+
|
|
94
|
+
// Action 1: Set the Extractor for the transfer function
|
|
95
|
+
ERC20TransferExtractor transferExtractor = new ERC20TransferExtractor();
|
|
96
|
+
bytes4 transferSelector = myToken.transfer.selector;
|
|
97
|
+
policyEngine.setExtractor(transferSelector, address(transferExtractor));
|
|
98
|
+
|
|
99
|
+
// Action 2: Add a policy that uses an extracted parameter
|
|
100
|
+
VolumePolicy volumePolicy = new VolumePolicy();
|
|
101
|
+
// ... initialize volumePolicy with a volume limit ...
|
|
102
|
+
|
|
103
|
+
// Create a list of the parameter names this policy needs.
|
|
104
|
+
// These names MUST match the names defined in the ERC20TransferExtractor.
|
|
105
|
+
bytes32[] memory policyParams = new bytes32[](1);
|
|
106
|
+
policyParams[0] = transferExtractor.PARAM_VALUE(); // e.g., keccak256("value")
|
|
107
|
+
|
|
108
|
+
// Add the policy and specify the parameters it requires
|
|
109
|
+
policyEngine.addPolicy(
|
|
110
|
+
address(myToken),
|
|
111
|
+
transferSelector,
|
|
112
|
+
address(volumePolicy),
|
|
113
|
+
policyParams
|
|
114
|
+
);
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The `PolicyEngine` will now automatically call the `ERC20TransferExtractor`, find the `value` parameter, and pass it to the `VolumePolicy` for every `transfer` call.
|
|
118
|
+
|
|
119
|
+
### Step 2.3: Reusing a Policy on a Different Function
|
|
120
|
+
|
|
121
|
+
A powerful feature of the system is policy reuse. You can apply the same policy contract to multiple different functions, even if those functions have different parameters.
|
|
122
|
+
|
|
123
|
+
**The Goal:** Imagine you have a [`SanctionsPolicy`](../../../getting_started/advanced/SanctionsPolicy.sol) already checking the `to` address on `transfer` calls. Now, you also want to block minting directly to a sanctioned address.
|
|
124
|
+
|
|
125
|
+
**The Action:** You will call `addPolicy` again, this time targeting the `mint` selector. You will provide the name of the parameter from the `mint` function's extractor that corresponds to the address you want to check.
|
|
126
|
+
|
|
127
|
+
```solidity
|
|
128
|
+
// --- In your deployment script ---
|
|
129
|
+
|
|
130
|
+
// Assume `sanctionsPolicy` and `mintBurnExtractor` are already deployed.
|
|
131
|
+
|
|
132
|
+
// 1. Define WHICH parameter the SanctionsPolicy needs for THIS attachment.
|
|
133
|
+
// The mint function's extractor calls the recipient parameter "account".
|
|
134
|
+
bytes32[] memory mintSanctionsParams = new bytes32[](1);
|
|
135
|
+
mintSanctionsParams[0] = mintBurnExtractor.PARAM_ACCOUNT(); // This is keccak256("account")
|
|
136
|
+
|
|
137
|
+
// 2. Add the SAME sanctionsPolicy to the MINT selector.
|
|
138
|
+
policyEngine.addPolicy(
|
|
139
|
+
address(myToken),
|
|
140
|
+
myToken.mint.selector, // Applying to the MINT function
|
|
141
|
+
address(sanctionsPolicy), // Re-using the same deployed policy instance
|
|
142
|
+
mintSanctionsParams // Specifying the "account" parameter is needed here
|
|
143
|
+
);
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The same `sanctionsPolicy` instance now protects both the `transfer` and `mint` functions, receiving the correct address to check from two different extractors, all configured through the `PolicyEngine`.
|
|
147
|
+
|
|
148
|
+
## 3. Setting a Default Result
|
|
149
|
+
|
|
150
|
+
**The Goal:** You want to define the engine's behavior for the case where a transaction passes through all of a function's policies without any of them returning a definitive `Allowed` or reverting `PolicyRejected`.
|
|
151
|
+
|
|
152
|
+
**The Recommendation:** For most development and production scenarios, it is **recommended to set the default to `Allowed`**.
|
|
153
|
+
|
|
154
|
+
**The Action:** In your deployment script, call `policyEngine.setDefaultPolicyAllow()`.
|
|
155
|
+
|
|
156
|
+
```solidity
|
|
157
|
+
// --- In your deployment script ---
|
|
158
|
+
|
|
159
|
+
// Set the default result for the entire engine.
|
|
160
|
+
policyEngine.setDefaultPolicyAllow(true);
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Rationale and Alternative Approaches
|
|
164
|
+
|
|
165
|
+
There are two primary philosophies for the default behavior of a policy system.
|
|
166
|
+
|
|
167
|
+
1. **Default `Allowed` is `true` (Recommended):**
|
|
168
|
+
|
|
169
|
+
- **Pros:** This approach maintains the regular, expected behavior of your contract's functions until you decide which specific protections are required. It allows you to start with a working system and progressively layer on restrictions (reject policies that revert) as needed.
|
|
170
|
+
- **Cons:** You must be diligent in protecting all new, sensitive functions. If you add a protected function but forget to add a policy, it will be open by default.
|
|
171
|
+
|
|
172
|
+
2. **Default `Allowed` is `false` (Alternative "Fail-Safe" Approach):**
|
|
173
|
+
- **Pros:** This is a more aggressive security posture. It ensures that no transaction can pass unless a policy explicitly permits it, which prevents new or misconfigured functions from being accidentally exposed.
|
|
174
|
+
- **Cons:** This model forces you to have at least one policy in every chain that returns `Allowed`, which can add complexity.
|
|
175
|
+
|
|
176
|
+
## 4. Using the `context` Parameter
|
|
177
|
+
|
|
178
|
+
**The Goal:** You need to pass arbitrary, transaction-specific data (like an offchain signature or Merkle proof) to a policy for validation.
|
|
179
|
+
|
|
180
|
+
**The Actions:** There are two methods to achieve this, depending on your use case. The recommended method is to pass the `context` directly as a function argument.
|
|
181
|
+
|
|
182
|
+
### Step 4.1: The Custom Policy (Common to Both Methods)
|
|
183
|
+
|
|
184
|
+
First, write a policy that is designed to receive and decode data from the `context` argument of its `run()` function. This `SignaturePolicy` is a common example.
|
|
185
|
+
|
|
186
|
+
```solidity
|
|
187
|
+
// Assumes usage of OpenZeppelin's ECDSA library
|
|
188
|
+
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
|
|
189
|
+
import {IPolicyEngine} from "@chainlink/policy-management/interfaces/IPolicyEngine.sol";
|
|
190
|
+
|
|
191
|
+
contract SignaturePolicy is Policy {
|
|
192
|
+
address public requiredSigner;
|
|
193
|
+
|
|
194
|
+
// ... constructor to set the requiredSigner ...
|
|
195
|
+
|
|
196
|
+
function run(
|
|
197
|
+
address caller,
|
|
198
|
+
address, // subject
|
|
199
|
+
bytes4, // selector
|
|
200
|
+
bytes[] calldata, // parameters
|
|
201
|
+
bytes calldata context
|
|
202
|
+
) public view virtual override returns (IPolicyEngine.PolicyResult) {
|
|
203
|
+
require(context.length > 0, "SignaturePolicy: context cannot be empty");
|
|
204
|
+
|
|
205
|
+
// Create the same message hash that was signed offchain
|
|
206
|
+
bytes32 messageHash = keccak256(abi.encodePacked("I approve this transaction for:", caller));
|
|
207
|
+
bytes32 signedHash = ECDSA.toEthSignedMessageHash(messageHash);
|
|
208
|
+
|
|
209
|
+
address recoveredSigner = ECDSA.recover(signedHash, context);
|
|
210
|
+
|
|
211
|
+
if (recoveredSigner != requiredSigner) {
|
|
212
|
+
revert IPolicyEngine.PolicyRejected("SignaturePolicy: invalid signature");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return IPolicyEngine.PolicyResult.Continue;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Step 4.2: Passing Context Directly (Recommended Pattern)
|
|
221
|
+
|
|
222
|
+
For your own custom functions, the cleanest and safest pattern is to pass the `context` directly as a function argument using the `runPolicyWithContext` modifier.
|
|
223
|
+
|
|
224
|
+
**The Action:** Create a custom function that accepts `context` as its final argument and protect it with the `runPolicyWithContext` modifier.
|
|
225
|
+
|
|
226
|
+
```solidity
|
|
227
|
+
// In your custom contract...
|
|
228
|
+
contract MyCustomContract is PolicyProtected {
|
|
229
|
+
// ...
|
|
230
|
+
function doSomethingSpecialWithApproval(
|
|
231
|
+
uint256 arg1,
|
|
232
|
+
bytes calldata context // The extra data is a direct argument
|
|
233
|
+
)
|
|
234
|
+
public
|
|
235
|
+
runPolicyWithContext(context) // The modifier receives the context directly
|
|
236
|
+
{
|
|
237
|
+
// Your core logic here...
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// In your calling transaction (e.g., a script or front-end):
|
|
242
|
+
// The context (e.g., a signature) is prepared offchain
|
|
243
|
+
bytes memory signature = getSignatureFromOffChainService(sender);
|
|
244
|
+
|
|
245
|
+
// Make a single call to the protected function
|
|
246
|
+
myCustomContract.doSomethingSpecialWithApproval(123, signature);
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
This approach is recommended for new, custom functions as it is more explicit and gas-efficient.
|
|
250
|
+
|
|
251
|
+
### Step 4.3: Passing Context via `setContext` (For Standard Interfaces)
|
|
252
|
+
|
|
253
|
+
If you need to protect a function with a standard, unchangeable signature (like ERC20 `transfer`), you **must** use the two-step `setContext` method.
|
|
254
|
+
|
|
255
|
+
**The Action:** The offchain client or calling contract must set the context for the `msg.sender` and then call the protected function within the same transaction.
|
|
256
|
+
|
|
257
|
+
```solidity
|
|
258
|
+
// In a test or script, using a multicall pattern:
|
|
259
|
+
|
|
260
|
+
// 1. Offchain: A trusted party signs a message approving the transaction for the sender.
|
|
261
|
+
// (This signature is the `context` bytes)
|
|
262
|
+
bytes memory signature = getSignatureFromOffChainService(sender);
|
|
263
|
+
|
|
264
|
+
// 2. Onchain: In a single atomic transaction:
|
|
265
|
+
// a. Set the context for the sender
|
|
266
|
+
myContract.setContext(signature);
|
|
267
|
+
// b. Call the protected function
|
|
268
|
+
myContract.protectedAction();
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
The `PolicyProtected` contract provides the `setContext` and `clearContext` functions. After a transaction that uses `setContext`, it is the responsibility of the caller or a subsequent process to clear the context to prevent it from being reused.
|
|
272
|
+
|
|
273
|
+
> **Security Note:** This method stores the context on a per-sender basis. It is **strongly recommended** to set and consume the context in the same atomic transaction to avoid potential race conditions or stale context being reused. **[Learn more about the security implications.](./SECURITY.md#4-context-handling-and-race-conditions)**
|
|
274
|
+
|
|
275
|
+
## Common Design Patterns
|
|
276
|
+
|
|
277
|
+
The Policy Management component enables several powerful, high-level design patterns for building robust and flexible systems.
|
|
278
|
+
|
|
279
|
+
### Pattern: Securing Core Infrastructure with Policies
|
|
280
|
+
|
|
281
|
+
The `PolicyEngine` is not just for protecting your main application contract (e.g., your token). You can and should use it to govern the administrative functions of the other components in your system, such as the `IdentityRegistry` and `CredentialRegistry` (learn more about the [Cross-Chain Identity](../../cross-chain-identity/README.md) component).
|
|
282
|
+
|
|
283
|
+
This creates a unified, consistent security model for your entire dApp ecosystem.
|
|
284
|
+
|
|
285
|
+
- **The Goal:** Ensure that only authorized addresses can add or remove credentials in the `CredentialRegistry`.
|
|
286
|
+
- **The Implementation:**
|
|
287
|
+
1. When you deploy the `CredentialRegistry`, you set its owner to be the `PolicyEngine` contract. This means all administrative actions must now pass through the engine.
|
|
288
|
+
2. You deploy a policy, such as `OnlyAuthorizedSenderPolicy`, which maintains a list of trusted addresses (e.g., your KYC provider).
|
|
289
|
+
3. In the `PolicyEngine`, you use `addPolicy` to apply this `OnlyAuthorizedSenderPolicy` to the administrative functions of the `CredentialRegistry` (e.g., `registerCredential`, `removeCredential`).
|
|
290
|
+
4. **The Result:** The `CredentialRegistry` is now fully protected. No one, not even the original deployer, can manage credentials unless they are explicitly added to the authorized senders list in the policy, creating a robust and auditable separation of roles.
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
> **Note for Developers:** This file provides a complete, auto-generated list of all functions and interfaces. For a more practical, task-oriented guide with code examples, please see the **[API Guide](API_GUIDE.md)** first.
|
|
2
|
+
|
|
3
|
+
# API Reference: Policy Management
|
|
4
|
+
|
|
5
|
+
This document provides the complete, formal interface specifications for the Policy Management component.
|
|
6
|
+
|
|
7
|
+
## Core Interfaces
|
|
8
|
+
|
|
9
|
+
### IPolicyEngine
|
|
10
|
+
|
|
11
|
+
**File:** [`src/interfaces/IPolicyEngine.sol`](../src/interfaces/IPolicyEngine.sol)
|
|
12
|
+
|
|
13
|
+
The `IPolicyEngine` interface defines the core functions for the central orchestrator that manages and executes policies.
|
|
14
|
+
|
|
15
|
+
#### Enums and Structs
|
|
16
|
+
|
|
17
|
+
```solidity
|
|
18
|
+
enum PolicyResult {
|
|
19
|
+
None,
|
|
20
|
+
Allowed,
|
|
21
|
+
Continue
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
struct Parameter {
|
|
25
|
+
bytes32 name;
|
|
26
|
+
bytes value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
struct Payload {
|
|
30
|
+
bytes4 selector;
|
|
31
|
+
address sender;
|
|
32
|
+
bytes data;
|
|
33
|
+
bytes context;
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
#### Key Functions
|
|
38
|
+
|
|
39
|
+
```solidity
|
|
40
|
+
interface IPolicyEngine is IERC165 {
|
|
41
|
+
function attach() external;
|
|
42
|
+
function detach() external;
|
|
43
|
+
|
|
44
|
+
function setExtractor(bytes4 selector, address extractor) external;
|
|
45
|
+
function setExtractors(bytes4[] calldata selectors, address extractor) external;
|
|
46
|
+
function getExtractor(bytes4 selector) external view returns (address);
|
|
47
|
+
|
|
48
|
+
function setPolicyMapper(address policy, address mapper) external;
|
|
49
|
+
function getPolicyMapper(address policy) external view returns (address);
|
|
50
|
+
|
|
51
|
+
function addPolicy(address target, bytes4 selector, address policy, bytes32[] calldata policyParameterNames) external;
|
|
52
|
+
function addPolicyAt(address target, bytes4 selector, address policy, bytes32[] calldata policyParameterNames, uint256 position) external;
|
|
53
|
+
function removePolicy(address target, bytes4 selector, address policy) external;
|
|
54
|
+
function getPolicies(address target, bytes4 selector) external view returns (address[] memory);
|
|
55
|
+
|
|
56
|
+
function setDefaultPolicyAllow(bool defaultAllow) external;
|
|
57
|
+
function setTargetDefaultPolicyAllow(address target, bool defaultAllow) external;
|
|
58
|
+
|
|
59
|
+
function check(Payload calldata payload) external view;
|
|
60
|
+
function run(Payload calldata payload) external;
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### IPolicy
|
|
65
|
+
|
|
66
|
+
**File:** [`src/interfaces/IPolicy.sol`](../src/interfaces/IPolicy.sol)
|
|
67
|
+
|
|
68
|
+
The `IPolicy` interface is the standard for all policy contracts. Each policy must implement this interface to be compatible with the `PolicyEngine`.
|
|
69
|
+
|
|
70
|
+
#### Key Functions
|
|
71
|
+
|
|
72
|
+
```solidity
|
|
73
|
+
interface IPolicy is IERC165 {
|
|
74
|
+
function run(
|
|
75
|
+
address caller,
|
|
76
|
+
address subject,
|
|
77
|
+
bytes4 selector,
|
|
78
|
+
bytes[] calldata parameters,
|
|
79
|
+
bytes calldata context
|
|
80
|
+
) external view returns (IPolicyEngine.PolicyResult);
|
|
81
|
+
|
|
82
|
+
function postRun(
|
|
83
|
+
address caller,
|
|
84
|
+
address subject,
|
|
85
|
+
bytes4 selector,
|
|
86
|
+
bytes[] calldata parameters,
|
|
87
|
+
bytes calldata context
|
|
88
|
+
) external;
|
|
89
|
+
|
|
90
|
+
function onInstall(bytes4 selector) external;
|
|
91
|
+
function onUninstall(bytes4 selector) external;
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### IPolicyProtected
|
|
96
|
+
|
|
97
|
+
**File:** [`src/interfaces/IPolicyProtected.sol`](../src/interfaces/IPolicyProtected.sol)
|
|
98
|
+
|
|
99
|
+
The `IPolicyProtected` interface must be implemented by any contract that wishes to be protected by a `PolicyEngine`.
|
|
100
|
+
|
|
101
|
+
#### Key Functions
|
|
102
|
+
|
|
103
|
+
```solidity
|
|
104
|
+
interface IPolicyProtected is IERC165 {
|
|
105
|
+
function attachPolicyEngine(address policyEngine) external;
|
|
106
|
+
function getPolicyEngine() external view returns (address);
|
|
107
|
+
function setContext(bytes calldata context) external;
|
|
108
|
+
function getContext() external view returns (bytes memory);
|
|
109
|
+
function clearContext() external;
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Helper Interfaces
|
|
114
|
+
|
|
115
|
+
### IExtractor
|
|
116
|
+
|
|
117
|
+
**File:** [`src/interfaces/IExtractor.sol`](../src/interfaces/IExtractor.sol)
|
|
118
|
+
|
|
119
|
+
The `IExtractor` interface is the standard for contracts that parse transaction calldata into named parameters for the `PolicyEngine`.
|
|
120
|
+
|
|
121
|
+
#### Key Functions
|
|
122
|
+
|
|
123
|
+
```solidity
|
|
124
|
+
interface IExtractor is IERC165 {
|
|
125
|
+
function extract(IPolicyEngine.Payload calldata payload) external view returns (IPolicyEngine.Parameter[] memory);
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### IMapper
|
|
130
|
+
|
|
131
|
+
**File:** [`src/interfaces/IMapper.sol`](../src/interfaces/IMapper.sol)
|
|
132
|
+
|
|
133
|
+
The `IMapper` interface is the standard for custom mapper contracts. A custom mapper is only needed for advanced scenarios where parameter data needs to be transformed before being passed to a policy.
|
|
134
|
+
|
|
135
|
+
#### Key Functions
|
|
136
|
+
|
|
137
|
+
```solidity
|
|
138
|
+
interface IMapper is IERC165 {
|
|
139
|
+
function map(
|
|
140
|
+
IPolicyEngine.Parameter[] calldata extractedParameters
|
|
141
|
+
) external view returns (bytes[] memory mappedParameters);
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Events
|
|
146
|
+
|
|
147
|
+
```solidity
|
|
148
|
+
event TargetAttached(address indexed target);
|
|
149
|
+
event TargetDetached(address indexed target);
|
|
150
|
+
event PolicyAdded(address indexed target, bytes4 indexed selector, address policy);
|
|
151
|
+
event PolicyRemoved(address indexed target, bytes4 indexed selector, address policy);
|
|
152
|
+
event ExtractorSet(bytes4 indexed selector, address indexed extractor);
|
|
153
|
+
event PolicyParametersSet(address indexed policy, bytes[] parameters);
|
|
154
|
+
event DefaultPolicyAllowSet(bool defaultAllow);
|
|
155
|
+
event TargetDefaultPolicyAllowSet(address indexed target, bool defaultAllow);
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Errors
|
|
159
|
+
|
|
160
|
+
```solidity
|
|
161
|
+
error TargetNotAttached(address target);
|
|
162
|
+
error TargetAlreadyAttached(address target);
|
|
163
|
+
error PolicyEngineUndefined();
|
|
164
|
+
error PolicyRunRejected(bytes4 selector, address policy, string rejectReason);
|
|
165
|
+
error PolicyRejected(string rejectReason);
|
|
166
|
+
error PolicyMapperError(address policy, bytes errorReason);
|
|
167
|
+
error PolicyRunError(bytes4 selector, address policy, bytes errorReason);
|
|
168
|
+
error PolicyRunUnauthorizedError(address account);
|
|
169
|
+
error PolicyPostRunError(bytes4 selector, address policy, bytes errorReason);
|
|
170
|
+
error UnsupportedSelector(bytes4 selector);
|
|
171
|
+
error InvalidConfiguration(string errorReason);
|
|
172
|
+
error ExtractorError(bytes4 selector, address extractor, bytes errorReason);
|
|
173
|
+
```
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Core Concepts: Policy Management
|
|
2
|
+
|
|
3
|
+
## Abstract
|
|
4
|
+
|
|
5
|
+
The Policy Management component provides a modular framework for defining, executing, and managing dynamic onchain rules. By separating policy logic from core contract logic, this component allows developers to add, remove, and compose compliance and operational rules without redeploying or modifying an application's primary business logic. It provides a robust, extensible, and secure foundation for building adaptable financial applications.
|
|
6
|
+
|
|
7
|
+
## Glossary of Key Terms
|
|
8
|
+
|
|
9
|
+
| Term | Role |
|
|
10
|
+
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
11
|
+
| **`PolicyProtected`** | An abstract contract that your application inherits from. It provides the `runPolicy` modifier which acts as the hook into the policy system. |
|
|
12
|
+
| **`PolicyEngine`** | The central onchain orchestrator. It holds the registry of all policies, executes them in order, and manages the default outcome. |
|
|
13
|
+
| **`Policy`** | A self-contained, modular contract that holds a single, specific rule (e.g., checking a user's role or limiting transaction volume). |
|
|
14
|
+
| **`Extractor`** | A helper contract that parses raw transaction `calldata` for a specific function signature and decodes it into a list of named parameters. |
|
|
15
|
+
| **`Mapper`** | An advanced, optional helper contract used to transform or combine parameters from an `Extractor` before they are passed to a `Policy`. |
|
|
16
|
+
|
|
17
|
+
## Core Architecture
|
|
18
|
+
|
|
19
|
+
The Policy Management architecture is designed to be as flexible and decoupled as possible. It is built on a few key components that work in concert to validate transactions.
|
|
20
|
+
|
|
21
|
+
- **`PolicyProtected`**: The bridge between your application and the compliance engine. By inheriting from this contract and using its `runPolicy` modifier on a function, you designate that function as being subject to the rules managed by the `PolicyEngine`.
|
|
22
|
+
|
|
23
|
+
- **`PolicyEngine`**: The brain of the system. For any protected function, the engine is responsible for:
|
|
24
|
+
|
|
25
|
+
1. Calling the correct `Extractor` to parse the transaction data.
|
|
26
|
+
2. Mapping the extracted parameters for each policy (either internally by default, or by delegating to a custom **[`Mapper`](#the-advanced-flow-custom-mappers)**).
|
|
27
|
+
3. Executing a chain of `Policy` contracts in a defined order.
|
|
28
|
+
4. Processing the `Allowed`, `Continue` result or `PolicyRejected` revert from each policy.
|
|
29
|
+
5. Applying a final, default result if no policy returns a definitive `Allowed` or revert.
|
|
30
|
+
|
|
31
|
+
- **`Policy`**: A modular rule. Each policy is a small, focused contract with a `run()` function that receives parameters and returns a verdict. This is where your specific business or compliance logic lives. The `Policy` can also have an optional `postRun()` function for state changes.
|
|
32
|
+
|
|
33
|
+
### The Policy Flow: A Step-by-Step Breakdown
|
|
34
|
+
|
|
35
|
+
The heart of the Policy Management component is the "chain of responsibility" pattern, where a transaction is passed through a series of checks before it is allowed to execute.
|
|
36
|
+
|
|
37
|
+
#### Policy Flow Diagram
|
|
38
|
+
|
|
39
|
+
```mermaid
|
|
40
|
+
sequenceDiagram
|
|
41
|
+
participant YourContract
|
|
42
|
+
participant PolicyEngine
|
|
43
|
+
participant Extractor
|
|
44
|
+
participant Policy1
|
|
45
|
+
participant Policy2
|
|
46
|
+
|
|
47
|
+
YourContract->>PolicyEngine: 1. run(payload)
|
|
48
|
+
Note right of YourContract: payload contains<br/>{selector, sender, data, context}
|
|
49
|
+
|
|
50
|
+
PolicyEngine->>Extractor: 2. extract(payload)
|
|
51
|
+
Extractor-->>PolicyEngine: 3. Returns named parameters (Parameter[])
|
|
52
|
+
|
|
53
|
+
Note over PolicyEngine: 4. Internally maps parameters for Policy 1
|
|
54
|
+
PolicyEngine->>Policy1: 5. run(caller, subject, selector, mapped_params, context)
|
|
55
|
+
|
|
56
|
+
alt Policy 1 Returns Continue
|
|
57
|
+
Policy1-->>PolicyEngine: 6a. Returns `Continue`
|
|
58
|
+
PolicyEngine->>Policy1: 7a. postRun(...) (optional)
|
|
59
|
+
|
|
60
|
+
Note over PolicyEngine: 8a. Proceeds to next policy.<br/>Internally maps parameters for Policy 2.
|
|
61
|
+
PolicyEngine->>Policy2: 9a. run(caller, subject, selector, mapped_params, context)
|
|
62
|
+
|
|
63
|
+
alt Final Decision
|
|
64
|
+
Policy2-->>PolicyEngine: 10a. Returns `Allowed` or reverts `PolicyRejected`
|
|
65
|
+
PolicyEngine->>Policy2: 11a. postRun(...) (if not reverted)
|
|
66
|
+
PolicyEngine-->>YourContract: 12a. Allows or Reverts Transaction
|
|
67
|
+
else No More Policies
|
|
68
|
+
Policy2-->>PolicyEngine: 10b. Returns `Continue`
|
|
69
|
+
PolicyEngine->>Policy2: 11b. postRun(...)
|
|
70
|
+
Note over PolicyEngine: 12b. No policies left, applying default result.
|
|
71
|
+
PolicyEngine-->>YourContract: 13b. Allows or Reverts based on default
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
else Policy 1 Makes Final Decision
|
|
75
|
+
Policy1-->>PolicyEngine: 6b. Returns `Allowed` or reverts `PolicyRejected`
|
|
76
|
+
PolicyEngine->>Policy1: 7b. postRun(...) (if not reverted)
|
|
77
|
+
Note over PolicyEngine: Skips all subsequent policies
|
|
78
|
+
PolicyEngine-->>YourContract: 8b. Allows or Reverts Transaction
|
|
79
|
+
end
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
1. **Diagram Step 1-3 — Invocation and Extraction**
|
|
83
|
+
The protected contract invokes `PolicyEngine.run(payload)`. The engine calls the designated **Extractor** for the function selector, which decodes the calldata into a full list of named parameters.
|
|
84
|
+
|
|
85
|
+
2. **Diagram Step 4-5 — First Policy Execution**
|
|
86
|
+
The engine's internal mapper prepares the specific parameters required by **`Policy1`**. The engine then calls `Policy1.run(...)`.
|
|
87
|
+
|
|
88
|
+
3. **Diagram Step 6-7 — Processing the Result**
|
|
89
|
+
The diagram now shows two main paths:
|
|
90
|
+
|
|
91
|
+
- **Policy 1 makes a final decision (Path 6b):** If `Policy1` returns `Allowed` the engine calls its optional `postRun(...)` function (if not reverted) and the process stops, skipping `Policy2` entirely.
|
|
92
|
+
- **Policy 1 defers (Path 6a):** If `Policy1` returns `Continue`, the engine calls its optional `postRun(...)` function and prepares to execute the next policy in the chain.
|
|
93
|
+
|
|
94
|
+
4. **Diagram Step 8-13 — Second Policy Execution**
|
|
95
|
+
Following Path 6a, the engine now prepares the parameters required by **`Policy2`** and calls its `run` function. The outcome of this second call (or any subsequent policy) will determine the final result, or the engine will use its default if `Policy2` also returns `Continue`.
|
|
96
|
+
|
|
97
|
+
5. **Branching Decisions & `postRun`**
|
|
98
|
+
- **reverts `PolicyRejected`**: The policy has found a definitive reason to block the transaction. The `PolicyEngine` immediately reverts the entire transaction. No `postRun` is called.
|
|
99
|
+
- **`Allowed`**: The engine then calls the policy's optional `postRun(...)` function (if implemented), which can be used to perform state changes. The engine then **skips** any remaining policies and allows the transaction.
|
|
100
|
+
- **`Continue`**: The engine calls the policy's optional `postRun(...)` function (if implemented), then moves to the next policy in the chain. If no policies remain, it applies the engine's default outcome.
|
|
101
|
+
|
|
102
|
+
### The Extractor and Mapper Pattern
|
|
103
|
+
|
|
104
|
+
A key design principle of the Policy Management component is the separation of concerns between **parsing data** and **enforcing rules**. This is achieved through Extractors and Mappers.
|
|
105
|
+
|
|
106
|
+
#### The Default Flow (95% of Use Cases)
|
|
107
|
+
|
|
108
|
+
For most scenarios, the process is simple and powerful:
|
|
109
|
+
|
|
110
|
+
1. You write an **Extractor** for a specific function signature (e.g., `ERC20TransferExtractor` for `transfer(address,uint256)`). This extractor is responsible for parsing _all_ the parameters you might need from that function's calldata (e.g., `to` and `value`). You register this one extractor for that function selector using `setExtractor`.
|
|
111
|
+
|
|
112
|
+
2. You then attach multiple **Policies** to that same function selector using `addPolicy`. The `PolicyEngine` has a built-in default mapper that works by **name matching**. When you call `addPolicy`, you provide a list of the parameter names that specific policy needs (e.g., `["to"]` for a sanctions policy, `["value"]` for a volume policy).
|
|
113
|
+
|
|
114
|
+
The engine handles the rest: it calls the single extractor, gets all the parameters, and then for each policy, it provides only the subset of parameters that policy requested by name.
|
|
115
|
+
|
|
116
|
+
This provides the best of both worlds: efficient data parsing (one extraction per transaction) and clean, decoupled policies.
|
|
117
|
+
|
|
118
|
+
#### The Advanced Flow (Custom Mappers)
|
|
119
|
+
|
|
120
|
+
A custom **Mapper** contract (`IMapper`) is only needed for rare, advanced scenarios where simple name-based mapping is not enough. This is for when you need to **transform or combine** extracted data before passing it to a policy.
|
|
121
|
+
|
|
122
|
+
For example, imagine a policy that needs a `usdValue`, but the extractor only provides `tokenAmount` and `tokenPrice`. You could write a custom mapper that:
|
|
123
|
+
|
|
124
|
+
1. Takes `tokenAmount` and `tokenPrice` as input.
|
|
125
|
+
2. Multiplies them to calculate `usdValue`.
|
|
126
|
+
3. Returns the `usdValue` in the `bytes[]` array for the policy.
|
|
127
|
+
|
|
128
|
+
You would then associate this custom mapper with that specific policy using `setPolicyMapper`, overriding the engine's default behavior for that policy only.
|
|
129
|
+
|
|
130
|
+
### The `context` Parameter: A Flexible Data Channel
|
|
131
|
+
|
|
132
|
+
Throughout the Policy Management interfaces, you will see a `bytes calldata context` parameter in functions like `run` and `postRun`. This parameter serves as a powerful, flexible data channel for passing arbitrary, transaction-specific information to your policies.
|
|
133
|
+
|
|
134
|
+
It is designed for situations where the data needed for a compliance check is **not** part of the protected function's arguments and is provided by the entity that initiates the transaction.
|
|
135
|
+
|
|
136
|
+
#### Common Use Cases for `context`
|
|
137
|
+
|
|
138
|
+
- **Offchain Signatures:** A user signs a message offchain (e.g., approving a high-value transaction), and the front-end passes this signature into the `context`. A policy can then decode the signature and use `ecrecover` to validate it.
|
|
139
|
+
- **Merkle Proofs:** To verify that an address is part of a large, dynamic allowlist stored offchain, the caller can provide a Merkle proof in the `context`. The policy would then validate this proof against a stored Merkle root.
|
|
140
|
+
- **Dynamic Risk Parameters:** An integrator could pass in dynamic, offchain risk scores or session data via the `context`, allowing policies to make more nuanced decisions.
|
|
141
|
+
|
|
142
|
+
#### How it Works: Two Methods for Passing Context
|
|
143
|
+
|
|
144
|
+
The `PolicyProtected` contract offers two ways to pass `context` data. The recommended approach is to pass it directly as a function argument, but a two-step method is also available for compatibility with standard interfaces.
|
|
145
|
+
|
|
146
|
+
1. **The `...WithContext` Method (Recommended for Custom Functions):**
|
|
147
|
+
For your own custom functions, the cleanest, safest, and most gas-efficient pattern is to pass the context directly as an argument. The `PolicyProtected` contract provides a `runPolicyWithContext(bytes)` modifier for this purpose.
|
|
148
|
+
|
|
149
|
+
- **Pros:** Cleaner code, safer (eliminates risk of stale context), more gas-efficient (one transaction).
|
|
150
|
+
- **Cons:** Only works for custom functions where you can define the signature.
|
|
151
|
+
- **Implementation:** The function signature would look like `myFunction(arg1, arg2, bytes calldata context)`, and the modifier would be `runPolicyWithContext(context)`.
|
|
152
|
+
|
|
153
|
+
2. **The `setContext` Method (For Standard Interfaces):**
|
|
154
|
+
This method should be used when you need to protect a function with a standard, unchangeable signature (like an ERC20 `transfer`). An external caller first calls `setContext(bytes)` and then immediately calls the protected function in the same transaction. The `runPolicy` modifier automatically retrieves this `context`, includes it in the `Payload`, and clears it after the transaction.
|
|
155
|
+
|
|
156
|
+
> **Security Note:** This method stores the context on a per-sender basis. It is **strongly recommended** to set and consume the context in the same atomic transaction to avoid potential race conditions or stale context being reused. **[Learn more about the security implications.](./SECURITY.md#4-context-handling-and-race-conditions)**
|