@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,477 @@
|
|
|
1
|
+
# ACE Getting Started: Integrate Policy-Based Compliance
|
|
2
|
+
|
|
3
|
+
This guide provides a fast path to integrating your smart contract with Chainlink Automated Compliance Engine (ACE).
|
|
4
|
+
|
|
5
|
+
**Why integrate ACE?** Prepare your contracts for future regulatory requirements without rewriting your core logic. Add, remove, or update compliance rules after deployment through modular policies.
|
|
6
|
+
|
|
7
|
+
**What you'll learn:** The 3-step pattern to make any contract policy-protected.
|
|
8
|
+
|
|
9
|
+
**What you'll build:** A simple vault with pauseable functions.
|
|
10
|
+
|
|
11
|
+
> **Building a token?** The pattern is identical—just use our audited [tokens](../packages/tokens) instead. We use a vault here to keep the example focused on ACE integration, not token complexity.
|
|
12
|
+
|
|
13
|
+
## A quick note before you start
|
|
14
|
+
|
|
15
|
+
This guide gets you hands-on fast. But if you find yourself asking "why?", these quick reads will help:
|
|
16
|
+
|
|
17
|
+
- **[What is Policy Management?](../packages/policy-management/README.md)** - Core concepts
|
|
18
|
+
- **[How do policies execute?](../packages/policy-management/docs/POLICY_ORDERING_GUIDE.md)** - Understanding `Allowed`, `Continue`, and `PolicyRejected` error
|
|
19
|
+
|
|
20
|
+
## Part 1: What you need to integrate ACE
|
|
21
|
+
|
|
22
|
+
Integrating ACE into your contract requires three key steps. Each step is simple and modular, allowing you to adopt ACE without rewriting your contract's core business logic.
|
|
23
|
+
|
|
24
|
+
### Step 1: Inherit from `PolicyProtected`
|
|
25
|
+
|
|
26
|
+
**What it is:** [`PolicyProtected`](../packages/policy-management/src/core/PolicyProtected.sol) is an abstract contract that provides the connection between your contract and the ACE compliance system.
|
|
27
|
+
|
|
28
|
+
**What you need to do:** Add `PolicyProtected` to your contract's inheritance list.
|
|
29
|
+
|
|
30
|
+
```solidity
|
|
31
|
+
import {PolicyProtected} from "@chainlink/policy-management/core/PolicyProtected.sol";
|
|
32
|
+
|
|
33
|
+
contract YourContract is PolicyProtected {
|
|
34
|
+
// Your contract code...
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
> **Note on Imports:** In this repo, the `@chainlink/...` imports work via Foundry remappings defined in `remappings.txt`. These point to the local `packages/` directory in this repository, not npm packages. If you're integrating ACE into your own project, ensure your `remappings.txt` includes:
|
|
39
|
+
>
|
|
40
|
+
> ```
|
|
41
|
+
> @chainlink/policy-management/=packages/policy-management/src/
|
|
42
|
+
> @chainlink/cross-chain-identity/=packages/cross-chain-identity/src/
|
|
43
|
+
> ```
|
|
44
|
+
|
|
45
|
+
**What this gives you:**
|
|
46
|
+
|
|
47
|
+
- Access to the `runPolicy` and `runPolicyWithContext` modifiers, which are the hooks into the policy system.
|
|
48
|
+
- Functions to attach and manage your contract's connection to a `PolicyEngine`.
|
|
49
|
+
- The ability to pass additional context data (like off-chain signatures) to your policies.
|
|
50
|
+
|
|
51
|
+
> **Important:** `PolicyProtected` is an upgradeable base contract, which means **your contract must be deployed through a proxy** (like `ERC1967Proxy`). See the [deployment script example](#the-deployment-script) below for the pattern.
|
|
52
|
+
|
|
53
|
+
### Step 2: Protect your functions with the `runPolicy` modifier
|
|
54
|
+
|
|
55
|
+
**What it is:** The `runPolicy` modifier is a function modifier that intercepts calls to your contract's functions and routes them through the `PolicyEngine` for validation before executing your core logic.
|
|
56
|
+
|
|
57
|
+
**What you need to do:** Add the `runPolicy` modifier to any function you want to protect with onchain compliance rules.
|
|
58
|
+
|
|
59
|
+
```solidity
|
|
60
|
+
function transfer(address to, uint256 amount) public runPolicy returns (bool) {
|
|
61
|
+
// Your transfer logic...
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function mint(address to, uint256 amount) public runPolicy {
|
|
65
|
+
// Your minting logic...
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**What this gives you:**
|
|
70
|
+
|
|
71
|
+
- **Flexible compliance enforcement:** Before your function's code runs, the `PolicyEngine` will check all registered policies for that function.
|
|
72
|
+
- **Zero core logic changes:** You don't modify your business logic. The modifier handles all compliance checks separately.
|
|
73
|
+
- **Dynamic updates:** You can add, remove, or reorder policies without ever touching this function again.
|
|
74
|
+
|
|
75
|
+
> **Note:** You can add the `runPolicy` modifier to any function in your contract. It works for standard functions (like `transfer`), administrative functions (like `mint`), or any custom function you define.
|
|
76
|
+
|
|
77
|
+
### Step 3: Deploy and configure a `PolicyEngine`
|
|
78
|
+
|
|
79
|
+
**What it is:** The [`PolicyEngine`](../packages/policy-management/src/core/PolicyEngine.sol) is the central orchestrator of your compliance system. It stores all your policies and executes them in order whenever a protected function is called.
|
|
80
|
+
|
|
81
|
+
**What you need to do:** Deploy and configure a `PolicyEngine`, then connect it to your contract and attach policies.
|
|
82
|
+
|
|
83
|
+
**Step 3a: Deploy and initialize the PolicyEngine**
|
|
84
|
+
|
|
85
|
+
```solidity
|
|
86
|
+
// Deploy the PolicyEngine implementation
|
|
87
|
+
PolicyEngine policyEngineImpl = new PolicyEngine();
|
|
88
|
+
|
|
89
|
+
// Encode the initialization data
|
|
90
|
+
bytes memory initData = abi.encodeWithSelector(
|
|
91
|
+
PolicyEngine.initialize.selector,
|
|
92
|
+
true, // defaultAllow: true = allow by default, false = reject by default
|
|
93
|
+
owner
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
// Deploy the proxy and initialize in one step
|
|
97
|
+
ERC1967Proxy proxy = new ERC1967Proxy(address(policyEngineImpl), initData);
|
|
98
|
+
PolicyEngine policyEngine = PolicyEngine(address(proxy));
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The `defaultAllow` parameter (boolean) sets what happens when no policies are attached or when all policies return `Continue`:
|
|
102
|
+
|
|
103
|
+
- `true` - Transaction proceeds (permissive default, recommended for gradual adoption)
|
|
104
|
+
- `false` - Transaction is blocked (restrictive default)
|
|
105
|
+
|
|
106
|
+
> **Note:** When policies return `Allowed`, the engine immediately allows the transaction and stops evaluating further policies. When a policy reverts with a `PolicyRejected` error, the entire transaction reverts immediately. Only `Continue` results proceed to the next policy in the chain. Learn more in [How It Works](../packages/policy-management/README.md#how-it-works-an-overview).
|
|
107
|
+
|
|
108
|
+
**Step 3b: Deploy your contract and connect it to the PolicyEngine**
|
|
109
|
+
|
|
110
|
+
```solidity
|
|
111
|
+
// Deploy your contract implementation
|
|
112
|
+
MyContract contractImpl = new MyContract();
|
|
113
|
+
|
|
114
|
+
// Encode the initialization data (connects to PolicyEngine)
|
|
115
|
+
bytes memory contractInitData = abi.encodeWithSelector(
|
|
116
|
+
MyContract.initialize.selector,
|
|
117
|
+
owner,
|
|
118
|
+
address(policyEngine)
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// Deploy the proxy
|
|
122
|
+
ERC1967Proxy contractProxy = new ERC1967Proxy(address(contractImpl), contractInitData);
|
|
123
|
+
MyContract myContract = MyContract(address(contractProxy));
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Your contract is now connected to the `PolicyEngine`, and the `runPolicy` modifiers will route function calls through this engine for validation.
|
|
127
|
+
|
|
128
|
+
**Step 3c: Deploy and attach policies to specific functions**
|
|
129
|
+
|
|
130
|
+
```solidity
|
|
131
|
+
// 1. Deploy a policy through a proxy
|
|
132
|
+
PausePolicy policyImpl = new PausePolicy();
|
|
133
|
+
bytes memory policyData = abi.encodeWithSelector(
|
|
134
|
+
Policy.initialize.selector,
|
|
135
|
+
address(policyEngine),
|
|
136
|
+
owner,
|
|
137
|
+
configData
|
|
138
|
+
);
|
|
139
|
+
ERC1967Proxy policyProxy = new ERC1967Proxy(address(policyImpl), policyData);
|
|
140
|
+
PausePolicy pausePolicy = PausePolicy(address(policyProxy));
|
|
141
|
+
|
|
142
|
+
// 2. Attach it to a specific function on your contract
|
|
143
|
+
policyEngine.addPolicy(
|
|
144
|
+
address(yourContract), // The protected contract
|
|
145
|
+
yourContract.transfer.selector, // The function to protect
|
|
146
|
+
address(pausePolicy), // The policy to enforce
|
|
147
|
+
new bytes32[](0) // Parameter names (if the policy needs them)
|
|
148
|
+
);
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
You can attach multiple policies to the same function—they'll execute in the order you add them.
|
|
152
|
+
|
|
153
|
+
**What this gives you:**
|
|
154
|
+
|
|
155
|
+
- **A modular compliance system:** Policies are separate contracts that can be added, removed, or upgraded independently. See a list of [ready-to-use policies](../packages/policy-management/src/policies/README.md) for common use cases or [create your own](../packages/policy-management/docs/CUSTOM_POLICIES_TUTORIAL.md).
|
|
156
|
+
- **Composable rules and fine-grained control:** Chain multiple policies together to create sophisticated compliance logic, and attach different policies to different functions. See the [Policy Ordering Guide](../packages/policy-management/docs/POLICY_ORDERING_GUIDE.md) for details on how policy ordering works and why it matters.
|
|
157
|
+
|
|
158
|
+
## Part 2: An example implementation
|
|
159
|
+
|
|
160
|
+
Now that you understand the integration requirements, here's a complete, working example. This example shows how to protect a simple vault contract's `deposit` and `withdraw` functions with a `PausePolicy`.
|
|
161
|
+
|
|
162
|
+
This example includes:
|
|
163
|
+
|
|
164
|
+
1. A simple vault contract that inherits from `PolicyProtected` ([`MyVault.sol`](./MyVault.sol)).
|
|
165
|
+
2. A deployment script that sets up the `PolicyEngine` and attaches a `PausePolicy` ([`DeployGettingStarted.s.sol`](../script/getting_started/DeployGettingStarted.s.sol)).
|
|
166
|
+
3. Test commands to demonstrate pausing and unpausing vault operations.
|
|
167
|
+
|
|
168
|
+
### The vault contract
|
|
169
|
+
|
|
170
|
+
Here's the vault contract ([`getting_started/MyVault.sol`](./MyVault.sol)):
|
|
171
|
+
|
|
172
|
+
```solidity
|
|
173
|
+
// SPDX-License-Identifier: MIT
|
|
174
|
+
pragma solidity 0.8.26;
|
|
175
|
+
|
|
176
|
+
import {PolicyProtected} from "@chainlink/policy-management/core/PolicyProtected.sol";
|
|
177
|
+
|
|
178
|
+
contract MyVault is PolicyProtected {
|
|
179
|
+
mapping(address => uint256) public deposits;
|
|
180
|
+
|
|
181
|
+
function initialize(address initialOwner, address policyEngine) public initializer {
|
|
182
|
+
__PolicyProtected_init(initialOwner, policyEngine);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// The runPolicy modifier protects this function
|
|
186
|
+
function deposit(uint256 amount) public runPolicy {
|
|
187
|
+
deposits[msg.sender] += amount;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// The runPolicy modifier protects this function too
|
|
191
|
+
function withdraw(uint256 amount) public runPolicy {
|
|
192
|
+
require(deposits[msg.sender] >= amount, "Insufficient balance");
|
|
193
|
+
deposits[msg.sender] -= amount;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Key takeaways:**
|
|
199
|
+
|
|
200
|
+
- Inheritance from `PolicyProtected` (an upgradeable base contract)
|
|
201
|
+
- `initialize()` function sets up the owner and connects to the `PolicyEngine`
|
|
202
|
+
- Multiple functions can be protected with the `runPolicy` modifier
|
|
203
|
+
- Each function can have different policies attached via the `PolicyEngine`
|
|
204
|
+
|
|
205
|
+
### The deployment script
|
|
206
|
+
|
|
207
|
+
Here's the deployment script ([`DeployGettingStarted.s.sol`](../script/getting_started/DeployGettingStarted.s.sol)):
|
|
208
|
+
|
|
209
|
+
> **Note on Proxy Deployment:** All ACE components must be deployed behind a proxy because they use OpenZeppelin's upgradeable contracts pattern (disabled constructors with initializers). This guide uses `ERC1967Proxy`, which enables upgradeability—you can update contract logic while preserving state and addresses. In production, you may also encounter minimal proxies (clones) for components that don't require upgradeability.
|
|
210
|
+
|
|
211
|
+
```solidity
|
|
212
|
+
// SPDX-License-Identifier: BUSL-1.1
|
|
213
|
+
pragma solidity 0.8.26;
|
|
214
|
+
|
|
215
|
+
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
|
|
216
|
+
import {Script} from "forge-std/Script.sol";
|
|
217
|
+
import {console} from "forge-std/console.sol";
|
|
218
|
+
import {MyVault} from "../getting_started/MyVault.sol";
|
|
219
|
+
import {PolicyEngine} from "@chainlink/policy-management/core/PolicyEngine.sol";
|
|
220
|
+
import {Policy} from "@chainlink/policy-management/core/Policy.sol";
|
|
221
|
+
import {PausePolicy} from "@chainlink/policy-management/policies/PausePolicy.sol";
|
|
222
|
+
import {IPolicyEngine} from "@chainlink/policy-management/interfaces/IPolicyEngine.sol";
|
|
223
|
+
|
|
224
|
+
contract DeployGettingStarted is Script {
|
|
225
|
+
function run() external {
|
|
226
|
+
uint256 deployerPK = vm.envUint("PRIVATE_KEY");
|
|
227
|
+
address deployer = vm.addr(deployerPK);
|
|
228
|
+
|
|
229
|
+
vm.startBroadcast(deployerPK);
|
|
230
|
+
|
|
231
|
+
// 1. Deploy the PolicyEngine through a proxy
|
|
232
|
+
PolicyEngine policyEngineImpl = new PolicyEngine();
|
|
233
|
+
bytes memory policyEngineData = abi.encodeWithSelector(
|
|
234
|
+
PolicyEngine.initialize.selector,
|
|
235
|
+
true, // defaultAllow = true (allow by default)
|
|
236
|
+
deployer
|
|
237
|
+
);
|
|
238
|
+
ERC1967Proxy policyEngineProxy = new ERC1967Proxy(address(policyEngineImpl), policyEngineData);
|
|
239
|
+
PolicyEngine policyEngine = PolicyEngine(address(policyEngineProxy));
|
|
240
|
+
|
|
241
|
+
// 2. Deploy your vault through a proxy
|
|
242
|
+
MyVault vaultImpl = new MyVault();
|
|
243
|
+
bytes memory vaultData = abi.encodeWithSelector(
|
|
244
|
+
MyVault.initialize.selector,
|
|
245
|
+
deployer,
|
|
246
|
+
address(policyEngine)
|
|
247
|
+
);
|
|
248
|
+
ERC1967Proxy vaultProxy = new ERC1967Proxy(address(vaultImpl), vaultData);
|
|
249
|
+
MyVault vault = MyVault(address(vaultProxy));
|
|
250
|
+
|
|
251
|
+
// 3. Deploy the PausePolicy through a proxy
|
|
252
|
+
PausePolicy pausePolicyImpl = new PausePolicy();
|
|
253
|
+
bytes memory pausePolicyConfig = abi.encode(false); // Not paused by default
|
|
254
|
+
bytes memory pausePolicyData = abi.encodeWithSelector(
|
|
255
|
+
Policy.initialize.selector,
|
|
256
|
+
address(policyEngine),
|
|
257
|
+
deployer,
|
|
258
|
+
pausePolicyConfig
|
|
259
|
+
);
|
|
260
|
+
ERC1967Proxy pausePolicyProxy = new ERC1967Proxy(address(pausePolicyImpl), pausePolicyData);
|
|
261
|
+
PausePolicy pausePolicy = PausePolicy(address(pausePolicyProxy));
|
|
262
|
+
|
|
263
|
+
// 4. Add the PausePolicy to both deposit and withdraw functions
|
|
264
|
+
policyEngine.addPolicy(
|
|
265
|
+
address(vault),
|
|
266
|
+
vault.deposit.selector,
|
|
267
|
+
address(pausePolicy),
|
|
268
|
+
new bytes32[](0) // No parameters needed for PausePolicy
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
policyEngine.addPolicy(
|
|
272
|
+
address(vault),
|
|
273
|
+
vault.withdraw.selector,
|
|
274
|
+
address(pausePolicy),
|
|
275
|
+
new bytes32[](0)
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
vm.stopBroadcast();
|
|
279
|
+
|
|
280
|
+
console.log("--- Deployed Contracts ---");
|
|
281
|
+
console.log("MyVault deployed at:", address(vault));
|
|
282
|
+
console.log("PolicyEngine deployed at:", address(policyEngine));
|
|
283
|
+
console.log("PausePolicy deployed at:", address(pausePolicy));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Testing your compliant vault
|
|
289
|
+
|
|
290
|
+
You can now test your policy-protected vault on a local Anvil chain.
|
|
291
|
+
|
|
292
|
+
#### Prerequisites
|
|
293
|
+
|
|
294
|
+
- [Node.js](https://nodejs.org/en/download/) (v18 or later)
|
|
295
|
+
- [Foundry](https://book.getfoundry.sh/getting-started/installation) (v0.3.0 or later)
|
|
296
|
+
- [pnpm](https://pnpm.io/installation)
|
|
297
|
+
|
|
298
|
+
#### Setup
|
|
299
|
+
|
|
300
|
+
1. **Clone and enter the repository:**
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
git clone https://github.com/smartcontractkit/chainlink-ace.git
|
|
304
|
+
cd chainlink-ace
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
2. **Install dependencies:**
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
pnpm install
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
3. **Build the project:**
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
pnpm build
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
#### Start Anvil Chain
|
|
320
|
+
|
|
321
|
+
From a new terminal, start a new anvil chain by running:
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
anvil
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
**Note:** Keep this terminal open - anvil runs on `http://localhost:8545` by default.
|
|
328
|
+
|
|
329
|
+
#### Deploy the System
|
|
330
|
+
|
|
331
|
+
Now you're ready to deploy your policy-protected vault:
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
export ETH_RPC_URL=http://localhost:8545
|
|
335
|
+
export PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
|
|
336
|
+
|
|
337
|
+
forge script script/getting_started/DeployGettingStarted.s.sol:DeployGettingStarted --rpc-url $ETH_RPC_URL --private-key $PRIVATE_KEY --broadcast
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
#### Test the compliance system
|
|
341
|
+
|
|
342
|
+
**Set up your environment** (replace with your deployed addresses from the logs above):
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
export VAULT_ADDRESS=<Your_Deployed_MyVault_Address>
|
|
346
|
+
export PAUSE_POLICY_ADDRESS=<Your_Deployed_PausePolicy_Address>
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
> **Tip:** After running the deploy command, you'll see the deployed addresses in the output logs:
|
|
350
|
+
>
|
|
351
|
+
> ```
|
|
352
|
+
> == Logs ==
|
|
353
|
+
> --- Deployed Contracts ---
|
|
354
|
+
> MyVault deployed at: 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9
|
|
355
|
+
> PolicyEngine deployed at: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
|
|
356
|
+
> PausePolicy deployed at: 0x5FC8d32690cc91D4c39d9d3abcBD16989F875707
|
|
357
|
+
> ```
|
|
358
|
+
>
|
|
359
|
+
> Use these addresses for `VAULT_ADDRESS` and `PAUSE_POLICY_ADDRESS`.
|
|
360
|
+
|
|
361
|
+
**Run the tests:**
|
|
362
|
+
|
|
363
|
+
1. **Make a deposit of 100:**
|
|
364
|
+
|
|
365
|
+
```bash
|
|
366
|
+
cast send $VAULT_ADDRESS "deposit(uint256)" 100 --private-key $PRIVATE_KEY
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
**Expected:** Success!
|
|
370
|
+
|
|
371
|
+
2. **Check your deposit:**
|
|
372
|
+
|
|
373
|
+
```bash
|
|
374
|
+
cast call $VAULT_ADDRESS "deposits(address)" 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 | cast --to-dec
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
**Expected:** 100
|
|
378
|
+
|
|
379
|
+
3. **Pause the vault:**
|
|
380
|
+
|
|
381
|
+
```bash
|
|
382
|
+
cast send $PAUSE_POLICY_ADDRESS "pause()" --private-key $PRIVATE_KEY
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
**Expected:** Success!
|
|
386
|
+
|
|
387
|
+
4. **Attempt a deposit:**
|
|
388
|
+
|
|
389
|
+
```bash
|
|
390
|
+
cast send $VAULT_ADDRESS "deposit(uint256)" 50 --private-key $PRIVATE_KEY
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
**Expected:** `Error: PolicyRunRejected` with reason "contract is paused"
|
|
394
|
+
|
|
395
|
+
5. **Attempt a withdrawal:**
|
|
396
|
+
|
|
397
|
+
```bash
|
|
398
|
+
cast send $VAULT_ADDRESS "withdraw(uint256)" 10 --private-key $PRIVATE_KEY
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
**Expected:** `Error: PolicyRunRejected` with reason "contract is paused"
|
|
402
|
+
|
|
403
|
+
6. **Unpause the vault:**
|
|
404
|
+
|
|
405
|
+
```bash
|
|
406
|
+
cast send $PAUSE_POLICY_ADDRESS "unpause()" --private-key $PRIVATE_KEY
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
**Expected:** Success!
|
|
410
|
+
|
|
411
|
+
7. **Withdraw funds:**
|
|
412
|
+
|
|
413
|
+
```bash
|
|
414
|
+
cast send $VAULT_ADDRESS "withdraw(uint256)" 10 --private-key $PRIVATE_KEY
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
**Expected:** Success!
|
|
418
|
+
|
|
419
|
+
8. **Verify the updated balance:**
|
|
420
|
+
|
|
421
|
+
```bash
|
|
422
|
+
cast call $VAULT_ADDRESS "deposits(address)" 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 | cast --to-dec
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
**Expected:** 90 (100 - 10)
|
|
426
|
+
|
|
427
|
+
## Next steps
|
|
428
|
+
|
|
429
|
+
You now understand the core ACE integration pattern:
|
|
430
|
+
|
|
431
|
+
1. Inherit from `PolicyProtected`
|
|
432
|
+
2. Protect functions with the `runPolicy` modifier
|
|
433
|
+
3. Deploy a `PolicyEngine` and attach policies
|
|
434
|
+
|
|
435
|
+
### Where to go from here
|
|
436
|
+
|
|
437
|
+
Choose your path based on what you want to build:
|
|
438
|
+
|
|
439
|
+
#### **Learn more about Policy Management**
|
|
440
|
+
|
|
441
|
+
You've seen one policy (`PausePolicy`) on two functions. Ready to level up?
|
|
442
|
+
|
|
443
|
+
**Understand the system:**
|
|
444
|
+
|
|
445
|
+
- **[How the Policy Flow Works](../packages/policy-management/README.md#how-it-works-an-overview)** - Understand `Allowed`, `Continue`, and `PolicyRejected` error
|
|
446
|
+
- **[Policy Ordering Matters](../packages/policy-management/docs/POLICY_ORDERING_GUIDE.md)** - Learn how policy execution order affects security
|
|
447
|
+
|
|
448
|
+
**Use Policies:**
|
|
449
|
+
|
|
450
|
+
- **[Policy Library](../packages/policy-management/src/policies/README.md)** - Explore ready-to-use policies: `MaxPolicy`, `VolumePolicy`, `OnlyOwnerPolicy`, `RoleBasedAccessControlPolicy`, and more
|
|
451
|
+
- **[Create a Custom Policy](../packages/policy-management/docs/CUSTOM_POLICIES_TUTORIAL.md)** - Step-by-step tutorial with boilerplate template
|
|
452
|
+
|
|
453
|
+
---
|
|
454
|
+
|
|
455
|
+
#### **Build a compliant token**
|
|
456
|
+
|
|
457
|
+
Don't reinvent the wheel. Use our audited, production-ready token implementations:
|
|
458
|
+
|
|
459
|
+
- **[ComplianceTokenERC20](../packages/tokens/erc-20/src/ComplianceTokenERC20.sol)**
|
|
460
|
+
- **[ComplianceTokenERC3643](../packages/tokens/erc-3643/src/ComplianceTokenERC3643.sol)**
|
|
461
|
+
- **[Example Deployment Scripts](../script/)** - See how to deploy and configure these tokens
|
|
462
|
+
|
|
463
|
+
---
|
|
464
|
+
|
|
465
|
+
#### **Add Cross-Chain Identity & credentials**
|
|
466
|
+
|
|
467
|
+
Ready to add identity and credential verification (KYC, AML, accreditation, etc.)? Integrate the Cross-Chain Identity component:
|
|
468
|
+
|
|
469
|
+
**Start with the advanced tutorial:**
|
|
470
|
+
|
|
471
|
+
- **[Advanced Getting Started: Tokenized Fund](./advanced/GETTING_STARTED_ADVANCED.md)** - Build a complete system with KYC checks, sanctions screening, and identity management across multiple roles
|
|
472
|
+
|
|
473
|
+
**Deep dive into identity:**
|
|
474
|
+
|
|
475
|
+
- **[Cross-Chain Identity Overview](../packages/cross-chain-identity/README.md)** - Understand CCIDs, credentials, and credential issuers
|
|
476
|
+
- **[Credential Flow Diagram](../packages/cross-chain-identity/docs/CREDENTIAL_FLOW.md)** - Visual walkthrough of the complete issuance process
|
|
477
|
+
- **[Security Considerations](../packages/cross-chain-identity/docs/SECURITY.md)** - Privacy, CCID correlation, and data protection
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
pragma solidity 0.8.26;
|
|
3
|
+
|
|
4
|
+
import {PolicyProtected} from "@chainlink/policy-management/core/PolicyProtected.sol";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @title MyVault
|
|
8
|
+
* @notice A simple vault contract with policy-protected deposits and withdrawals.
|
|
9
|
+
* @dev This example demonstrates the minimal integration required to make a contract compliant with ACE.
|
|
10
|
+
* Both the `deposit` and `withdraw` functions are protected with the `runPolicy` modifier.
|
|
11
|
+
* This contract is designed to be deployed behind a proxy for upgradeability.
|
|
12
|
+
*/
|
|
13
|
+
contract MyVault is PolicyProtected {
|
|
14
|
+
mapping(address => uint256) public deposits;
|
|
15
|
+
|
|
16
|
+
function initialize(address initialOwner, address policyEngine) public initializer {
|
|
17
|
+
__PolicyProtected_init(initialOwner, policyEngine);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @notice Deposits funds into the vault for the caller.
|
|
22
|
+
* @dev This function is protected by the `runPolicy` modifier, which routes the call
|
|
23
|
+
* through the PolicyEngine for validation before executing the deposit.
|
|
24
|
+
* @param amount The amount to deposit.
|
|
25
|
+
*/
|
|
26
|
+
function deposit(uint256 amount) public runPolicy {
|
|
27
|
+
deposits[msg.sender] += amount;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @notice Withdraws funds from the vault for the caller.
|
|
32
|
+
* @dev This function is protected by the `runPolicy` modifier, which routes the call
|
|
33
|
+
* through the PolicyEngine for validation before executing the withdrawal.
|
|
34
|
+
* @param amount The amount to withdraw.
|
|
35
|
+
*/
|
|
36
|
+
function withdraw(uint256 amount) public runPolicy {
|
|
37
|
+
require(deposits[msg.sender] >= amount, "Insufficient balance");
|
|
38
|
+
deposits[msg.sender] -= amount;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @notice Returns the total deposits in the vault.
|
|
43
|
+
* @return The sum of all deposits.
|
|
44
|
+
*/
|
|
45
|
+
function totalDeposits() public view returns (uint256) {
|
|
46
|
+
return address(this).balance;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# The main MMF Token Contract
|
|
2
|
+
export TOKEN_ADDRESS=0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE
|
|
3
|
+
# The Policy Engine that governs the token
|
|
4
|
+
export POLICY_ENGINE_ADDRESS=0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
|
|
5
|
+
# The registries for Identity and Credentials
|
|
6
|
+
export IDENTITY_REGISTRY_ADDRESS=0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9
|
|
7
|
+
export CREDENTIAL_REGISTRY_ADDRESS=0x5FC8d32690cc91D4c39d9d3abcBD16989F875707
|
|
8
|
+
# Our mock on-chain data source for sanctions
|
|
9
|
+
export SANCTIONS_LIST_ADDRESS=0x057ef64E23666F000b34aE31332854aCBd1c8544
|
|
10
|
+
# The policy that acts as the admin for the registries
|
|
11
|
+
export IDENTITY_ONLY_AUTHORIZED_POLICY_ADDRESS=0xa513E6E4b8f2a923D98304ec87F64353C4D5C853
|
|
12
|
+
|
|
13
|
+
# --- The Actors ---
|
|
14
|
+
# The Fund Manager (and default admin for this example)
|
|
15
|
+
export FUND_MANAGER_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
|
|
16
|
+
export FUND_MANAGER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
|
|
17
|
+
|
|
18
|
+
# The Verification Issuer (Anvil Account #9)
|
|
19
|
+
export CREDENTIAL_ISSUER_PRIVATE_KEY=0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6
|
|
20
|
+
export CREDENTIAL_ISSUER_ADDRESS=0xa0Ee7A142d267C1f36714E4a8F75612F20a79720
|
|
21
|
+
|
|
22
|
+
# The Sanctions List Provider (Anvil Account #3)
|
|
23
|
+
export SANCTIONS_PROVIDER_PRIVATE_KEY=0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6
|
|
24
|
+
export SANCTIONS_PROVIDER_ADDRESS=0x90F79bf6EB2c4f870365E785982E1f101E93b906
|
|
25
|
+
|
|
26
|
+
# Alice, a verified investor (Anvil Account #1)
|
|
27
|
+
export ALICE_PRIVATE_KEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
|
|
28
|
+
export ALICE_ADDRESS=0x70997970C51812dc3A010C7d01b50e0d17dc79C8
|
|
29
|
+
export ALICE_CCID=`cast keccak "ALICE_INVESTOR_ID"`
|
|
30
|
+
|
|
31
|
+
# Charlie, a verified but sanctioned investor (Anvil Account #2)
|
|
32
|
+
export CHARLIE_ADDRESS=0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC
|
|
33
|
+
export CHARLIE_CCID=`cast keccak "CHARLIE_INVESTOR_ID"`
|
|
34
|
+
|
|
35
|
+
# The required KYC credential
|
|
36
|
+
export KYC_CREDENTIAL=`cast keccak "common.kyc"`
|