Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ cache_hardhat
artifacts
*.lock

# Track the Soroban lockfile for reproducible contract builds
!soroban/Cargo.lock

#Foundry files
out
cache
Expand Down
55 changes: 55 additions & 0 deletions script/asset-compliance/DeployFreezableStablecoin.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.28;

import {Script} from "forge-std/Script.sol";
import {console2} from "forge-std/console2.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";

import {FreezableStablecoin} from "../../src/examples/asset-compliance/FreezableStablecoin.sol";

/**
* @title DeployFreezableStablecoin
* @notice Deploys {FreezableStablecoin} behind an ERC-1967 proxy with separate role holders.
* @dev `ADMIN` is required (fail-closed): DEFAULT_ADMIN_ROLE also authorizes upgrades, so it must
* never fall back to an implicit address. By default `FREEZE_MANAGER_ROLE` is assigned to
* Predicate's freezer so the asset is enrollment-ready; override any holder via env vars.
* Seize/mint/burn/pause default to the admin and should be moved to dedicated issuer keys in
* production.
*
* Usage:
* ADMIN=0x... forge script script/asset-compliance/DeployFreezableStablecoin.s.sol \
* --rpc-url $RPC_URL --broadcast
*/
contract DeployFreezableStablecoin is Script {
/// @notice Predicate's authorized freezer across all EVM chains (docs.predicate.io/v2/assets).
address public constant PREDICATE_FREEZER = 0x363c256D368277BBFaf6EaF65beE123a7AdbA464;

function run() external returns (FreezableStablecoin token, address proxy) {
address admin = vm.envAddress("ADMIN"); // required: never default the upgrade authority
address freezeManager = vm.envOr("FREEZE_MANAGER", PREDICATE_FREEZER);
address pauser = vm.envOr("PAUSER", admin);
address seizeManager = vm.envOr("SEIZE_MANAGER", admin);
address minter = vm.envOr("MINTER", admin);
address burner = vm.envOr("BURNER", admin);

string memory name_ = vm.envOr("TOKEN_NAME", string("Compliant USD"));
string memory symbol_ = vm.envOr("TOKEN_SYMBOL", string("cUSD"));

vm.startBroadcast();

FreezableStablecoin impl = new FreezableStablecoin();
bytes memory initData = abi.encodeCall(
FreezableStablecoin.initialize, (name_, symbol_, admin, freezeManager, pauser, seizeManager, minter, burner)
);
ERC1967Proxy proxy_ = new ERC1967Proxy(address(impl), initData);

vm.stopBroadcast();

proxy = address(proxy_);
token = FreezableStablecoin(proxy);

console2.log("FreezableStablecoin implementation:", address(impl));
console2.log("FreezableStablecoin proxy:", proxy);
console2.log("FREEZE_MANAGER_ROLE -> ", freezeManager);
}
}
55 changes: 55 additions & 0 deletions script/asset-compliance/GrantFreezeManager.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.28;

import {Script} from "forge-std/Script.sol";
import {console2} from "forge-std/console2.sol";

import {FreezableStablecoin} from "../../src/examples/asset-compliance/FreezableStablecoin.sol";

/**
* @title GrantFreezeManager
* @notice Grants / revokes / verifies Predicate's freezer on a deployed asset — the onchain step
* that makes a token enforceable by Predicate asset compliance.
* @dev `FREEZE_MANAGER_ROLE` is the ONLY authority Predicate needs. It is revocable in one
* transaction (`revoke`), verifiable (`check`), and never confers seize/mint/burn/pause/upgrade.
*
* Grant: TOKEN=0x.. forge script script/asset-compliance/GrantFreezeManager.s.sol --sig "run()" --rpc-url $RPC_URL --broadcast
* Revoke: TOKEN=0x.. forge script script/asset-compliance/GrantFreezeManager.s.sol --sig "revoke()" --rpc-url $RPC_URL --broadcast
* Check: TOKEN=0x.. forge script script/asset-compliance/GrantFreezeManager.s.sol --sig "check()" --rpc-url $RPC_URL
*
* Equivalent `cast` (mirrors https://docs.predicate.io/v2/assets/get-started):
* cast send <TOKEN> "grantRole(bytes32,address)" $(cast keccak "FREEZE_MANAGER_ROLE") \
* 0x363c256D368277BBFaf6EaF65beE123a7AdbA464 --rpc-url $RPC_URL --private-key $PK
* cast call <TOKEN> "hasRole(bytes32,address)(bool)" $(cast keccak "FREEZE_MANAGER_ROLE") \
* 0x363c256D368277BBFaf6EaF65beE123a7AdbA464 --rpc-url $RPC_URL
*/
contract GrantFreezeManager is Script {
/// @notice Predicate's authorized freezer across all EVM chains.
address public constant PREDICATE_FREEZER = 0x363c256D368277BBFaf6EaF65beE123a7AdbA464;

function run() external {
FreezableStablecoin token = _token();
vm.startBroadcast();
token.grantRole(token.FREEZE_MANAGER_ROLE(), PREDICATE_FREEZER);
vm.stopBroadcast();
console2.log("Granted FREEZE_MANAGER_ROLE to Predicate freezer on", address(token));
}

function revoke() external {
FreezableStablecoin token = _token();
vm.startBroadcast();
token.revokeRole(token.FREEZE_MANAGER_ROLE(), PREDICATE_FREEZER);
vm.stopBroadcast();
console2.log("Revoked FREEZE_MANAGER_ROLE from Predicate freezer on", address(token));
}

function check() external view returns (bool granted) {
FreezableStablecoin token = _token();
granted = token.hasRole(token.FREEZE_MANAGER_ROLE(), PREDICATE_FREEZER);
console2.log("Predicate freezer holds FREEZE_MANAGER_ROLE:", granted);
}

function _token() internal view returns (FreezableStablecoin) {
return FreezableStablecoin(vm.envAddress("TOKEN"));
}
}
Loading
Loading