mirror of
https://github.com/0xbow-io/privacy-pools-core.git
synced 2026-05-04 03:01:18 -04:00
391 lines
12 KiB
Solidity
391 lines
12 KiB
Solidity
// SPDX-License-Identifier: Apache-2.0
|
|
pragma solidity 0.8.28;
|
|
|
|
import {ERC1967Proxy} from '@oz/proxy/ERC1967/ERC1967Proxy.sol';
|
|
import {IERC20} from '@oz/token/ERC20/ERC20.sol';
|
|
import {Script} from 'forge-std/Script.sol';
|
|
// intentionally using console for deployment logging
|
|
|
|
import {Strings} from '@oz/utils/Strings.sol';
|
|
import {stdJson} from 'forge-std/StdJson.sol';
|
|
import {Vm, VmSafe} from 'forge-std/Vm.sol';
|
|
import {console} from 'forge-std/console.sol';
|
|
|
|
import {Constants} from 'contracts/lib/Constants.sol';
|
|
import {DeployLib} from 'contracts/lib/DeployLib.sol';
|
|
|
|
import {IPrivacyPool} from 'interfaces/IPrivacyPool.sol';
|
|
import {ICreateX} from 'interfaces/external/ICreateX.sol';
|
|
|
|
import {Entrypoint} from 'contracts/Entrypoint.sol';
|
|
import {PrivacyPoolComplex} from 'contracts/implementations/PrivacyPoolComplex.sol';
|
|
import {PrivacyPoolSimple} from 'contracts/implementations/PrivacyPoolSimple.sol';
|
|
import {CommitmentVerifier} from 'contracts/verifiers/CommitmentVerifier.sol';
|
|
import {WithdrawalVerifier} from 'contracts/verifiers/WithdrawalVerifier.sol';
|
|
|
|
/*///////////////////////////////////////////////////////////////
|
|
BASE DEPLOY SCRIPT
|
|
//////////////////////////////////////////////////////////////*/
|
|
|
|
/**
|
|
* @notice Abstract script to deploy the PrivacyPool protocol.
|
|
* @dev Assets and chain specific configurations must be defined in a parent contract.
|
|
*/
|
|
abstract contract DeployProtocol is Script {
|
|
using stdJson for string;
|
|
|
|
// @notice Struct for Pool deployment and configuration
|
|
struct PoolConfig {
|
|
string symbol;
|
|
IERC20 asset;
|
|
uint256 minimumDepositAmount;
|
|
uint256 vettingFeeBPS;
|
|
uint256 maxRelayFeeBPS;
|
|
}
|
|
|
|
// @notice Struct for recording deployment data
|
|
struct DeploymentData {
|
|
string contractName;
|
|
address contractAddress;
|
|
address deployer;
|
|
uint256 deploymentBlock;
|
|
uint256 scope;
|
|
address asset;
|
|
bytes constructorArgs;
|
|
}
|
|
|
|
error ChainIdAndRPCMismatch();
|
|
|
|
// @notice Deployed Entrypoint
|
|
Entrypoint public entrypoint;
|
|
// @notice Deployed Groth16 Withdrawal Verifier
|
|
address public withdrawalVerifier;
|
|
// @notice Deployed Groth16 Ragequit Verifier
|
|
address public ragequitVerifier;
|
|
|
|
// @notice Initial Entrypoint `ONWER_ROLE`
|
|
address public owner;
|
|
// @notice Initial Entrypoint `POSTMAN_ROLE`
|
|
address public postman;
|
|
|
|
address public deployer;
|
|
|
|
// @notice CreateX Singleton
|
|
ICreateX public constant CreateX = ICreateX(0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed);
|
|
|
|
// @notice Native asset pool configuration
|
|
PoolConfig internal _nativePoolConfig;
|
|
// @notice ERC20 pools configurations
|
|
PoolConfig[] internal _tokenPoolConfigs;
|
|
|
|
// @notice Array to store deployment data
|
|
DeploymentData[] internal _deploymentData;
|
|
|
|
function setUp() public virtual {
|
|
owner = vm.envAddress('OWNER_ADDRESS');
|
|
postman = vm.envAddress('POSTMAN_ADDRESS');
|
|
|
|
deployer = vm.envAddress('DEPLOYER_ADDRESS');
|
|
}
|
|
|
|
// @dev Must be called with the `--account` flag which acts as the caller
|
|
function run() public virtual {
|
|
vm.startBroadcast(deployer);
|
|
|
|
// Clear deployment data array
|
|
delete _deploymentData;
|
|
|
|
// Deploy verifiers
|
|
_deployGroth16Verifiers();
|
|
// Deploy Entrypoint
|
|
_deployEntrypoint();
|
|
|
|
// Deploy the native asset pool
|
|
_deploySimplePool(_nativePoolConfig);
|
|
|
|
// Deploy the ERC20 pools
|
|
for (uint256 _i; _i < _tokenPoolConfigs.length; ++_i) {
|
|
_deployComplexPool(_tokenPoolConfigs[_i]);
|
|
}
|
|
|
|
// Save deployment data to JSON file if in broadcast mode
|
|
if (vm.isContext(VmSafe.ForgeContext.ScriptBroadcast)) {
|
|
_saveDeploymentData();
|
|
}
|
|
|
|
vm.stopBroadcast();
|
|
}
|
|
|
|
function _deployGroth16Verifiers() private {
|
|
// Deploy WithdrawalVerifier using Create2
|
|
withdrawalVerifier = CreateX.deployCreate2(
|
|
DeployLib.salt(deployer, DeployLib.WITHDRAWAL_VERIFIER_SALT),
|
|
abi.encodePacked(type(WithdrawalVerifier).creationCode)
|
|
);
|
|
|
|
// intentionally using console for deployment feedback
|
|
console.log('Withdrawal Verifier deployed at: %s', withdrawalVerifier);
|
|
|
|
// Add to deployment data
|
|
_deploymentData.push(
|
|
DeploymentData({
|
|
contractName: 'WithdrawalVerifier',
|
|
contractAddress: withdrawalVerifier,
|
|
deployer: deployer,
|
|
deploymentBlock: block.number,
|
|
scope: 0,
|
|
asset: address(0),
|
|
constructorArgs: bytes('')
|
|
})
|
|
);
|
|
|
|
// Deploy CommitmentVerifier using Create2
|
|
ragequitVerifier = CreateX.deployCreate2(
|
|
DeployLib.salt(deployer, DeployLib.RAGEQUIT_VERIFIER_SALT),
|
|
abi.encodePacked(type(CommitmentVerifier).creationCode)
|
|
);
|
|
|
|
// intentionally using console for deployment feedback
|
|
console.log('Ragequit Verifier deployed at: %s', ragequitVerifier);
|
|
|
|
// Add to deployment data
|
|
_deploymentData.push(
|
|
DeploymentData({
|
|
contractName: 'CommitmentVerifier',
|
|
contractAddress: ragequitVerifier,
|
|
deployer: deployer,
|
|
deploymentBlock: block.number,
|
|
scope: 0,
|
|
asset: address(0),
|
|
constructorArgs: bytes('')
|
|
})
|
|
);
|
|
}
|
|
|
|
function _deployEntrypoint() private {
|
|
// Deploy Entrypoint implementation
|
|
address _impl =
|
|
CreateX.deployCreate2(DeployLib.salt(deployer, DeployLib.ENTRYPOINT_IMPL_SALT), type(Entrypoint).creationCode);
|
|
|
|
// Encode `initialize` call data
|
|
bytes memory _intializationData = abi.encodeCall(Entrypoint.initialize, (owner, postman));
|
|
|
|
// Deploy proxy and initialize
|
|
address _entrypoint = CreateX.deployCreate2(
|
|
DeployLib.salt(deployer, DeployLib.ENTRYPOINT_PROXY_SALT),
|
|
abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(_impl, _intializationData))
|
|
);
|
|
|
|
entrypoint = Entrypoint(payable(_entrypoint));
|
|
|
|
// intentionally using console for deployment feedback
|
|
console.log('Entrypoint deployed at: %s', address(entrypoint));
|
|
|
|
// Add implementation to deployment data
|
|
_deploymentData.push(
|
|
DeploymentData({
|
|
contractName: 'Entrypoint_Implementation',
|
|
contractAddress: _impl,
|
|
deployer: deployer,
|
|
deploymentBlock: block.number,
|
|
scope: 0,
|
|
asset: address(0),
|
|
constructorArgs: bytes('')
|
|
})
|
|
);
|
|
|
|
// Encode constructor args for proxy
|
|
bytes memory proxyArgs = abi.encode(_impl, _intializationData);
|
|
|
|
// Add proxy to deployment data
|
|
_deploymentData.push(
|
|
DeploymentData({
|
|
contractName: 'Entrypoint_Proxy',
|
|
contractAddress: address(entrypoint),
|
|
deployer: deployer,
|
|
deploymentBlock: block.number,
|
|
scope: 0,
|
|
asset: address(0),
|
|
constructorArgs: proxyArgs
|
|
})
|
|
);
|
|
}
|
|
|
|
function _deploySimplePool(PoolConfig memory _config) private {
|
|
// Encode constructor args
|
|
bytes memory constructorArgs = abi.encode(address(entrypoint), withdrawalVerifier, ragequitVerifier);
|
|
|
|
// Deploy pool with Create2
|
|
address _pool = CreateX.deployCreate2(
|
|
DeployLib.salt(deployer, DeployLib.SIMPLE_POOL_SALT),
|
|
abi.encodePacked(type(PrivacyPoolSimple).creationCode, constructorArgs)
|
|
);
|
|
|
|
// Register pool at entrypoint with defined configuration
|
|
entrypoint.registerPool(
|
|
IERC20(Constants.NATIVE_ASSET),
|
|
IPrivacyPool(_pool),
|
|
_config.minimumDepositAmount,
|
|
_config.vettingFeeBPS,
|
|
_config.maxRelayFeeBPS
|
|
);
|
|
|
|
// intentionally using console for deployment feedback
|
|
console.log('%s Pool deployed at: %s', _config.symbol, _pool);
|
|
|
|
// Get the actual scope from the deployed pool
|
|
uint256 poolScope = IPrivacyPool(_pool).SCOPE();
|
|
|
|
// Add to deployment data
|
|
_deploymentData.push(
|
|
DeploymentData({
|
|
contractName: string.concat('PrivacyPoolSimple_', _config.symbol),
|
|
contractAddress: _pool,
|
|
deployer: deployer,
|
|
deploymentBlock: block.number,
|
|
scope: poolScope,
|
|
asset: Constants.NATIVE_ASSET,
|
|
constructorArgs: constructorArgs
|
|
})
|
|
);
|
|
}
|
|
|
|
function _deployComplexPool(PoolConfig memory _config) private {
|
|
// Encode constructor args
|
|
bytes memory constructorArgs =
|
|
abi.encode(address(entrypoint), withdrawalVerifier, ragequitVerifier, address(_config.asset));
|
|
|
|
// Deploy pool with Create2
|
|
bytes11 _tokenSalt = bytes11(keccak256(abi.encodePacked(DeployLib.COMPLEX_POOL_SALT, _config.symbol)));
|
|
|
|
address _pool = CreateX.deployCreate2(
|
|
DeployLib.salt(deployer, _tokenSalt), abi.encodePacked(type(PrivacyPoolComplex).creationCode, constructorArgs)
|
|
);
|
|
|
|
// Register pool at entrypoint with defined configuration
|
|
entrypoint.registerPool(
|
|
_config.asset, IPrivacyPool(_pool), _config.minimumDepositAmount, _config.vettingFeeBPS, _config.maxRelayFeeBPS
|
|
);
|
|
|
|
// intentionally using console for deployment feedback
|
|
console.log('%s Pool deployed at: %s', _config.symbol, _pool);
|
|
|
|
// Get the actual scope from the deployed pool
|
|
uint256 poolScope = IPrivacyPool(_pool).SCOPE();
|
|
|
|
// Add to deployment data
|
|
_deploymentData.push(
|
|
DeploymentData({
|
|
contractName: string.concat('PrivacyPoolComplex_', _config.symbol),
|
|
contractAddress: _pool,
|
|
deployer: deployer,
|
|
deploymentBlock: block.number,
|
|
scope: poolScope,
|
|
asset: address(_config.asset),
|
|
constructorArgs: constructorArgs
|
|
})
|
|
);
|
|
}
|
|
|
|
function _saveDeploymentData() private {
|
|
// Manually create a well-formatted JSON string
|
|
string memory jsonString = '{';
|
|
|
|
// Add chainId
|
|
jsonString = string.concat(jsonString, '"chainId":', vm.toString(block.chainid), ',');
|
|
|
|
// Start contracts array
|
|
jsonString = string.concat(jsonString, '"contracts":[');
|
|
|
|
// Add each contract as a JSON object in the array
|
|
for (uint256 i = 0; i < _deploymentData.length; i++) {
|
|
// Start the contract object
|
|
jsonString = string.concat(jsonString, '{');
|
|
|
|
// Always include name, address, deployer, and deploymentBlock
|
|
jsonString = string.concat(
|
|
jsonString,
|
|
'"name":"',
|
|
_deploymentData[i].contractName,
|
|
'",',
|
|
'"address":"',
|
|
_addressToString(_deploymentData[i].contractAddress),
|
|
'",',
|
|
'"deployer":"',
|
|
_addressToString(_deploymentData[i].deployer),
|
|
'",',
|
|
'"deploymentBlock":',
|
|
vm.toString(_deploymentData[i].deploymentBlock)
|
|
);
|
|
|
|
// Only include scope if not 0
|
|
if (_deploymentData[i].scope != 0) {
|
|
jsonString = string.concat(jsonString, ',', '"scope":', vm.toString(_deploymentData[i].scope));
|
|
}
|
|
|
|
// Only include asset if not zero address
|
|
if (_deploymentData[i].asset != address(0)) {
|
|
jsonString = string.concat(jsonString, ',', '"asset":"', _addressToString(_deploymentData[i].asset), '"');
|
|
}
|
|
|
|
// Only include constructorArgs if not empty
|
|
if (_deploymentData[i].constructorArgs.length > 0) {
|
|
jsonString = string.concat(
|
|
jsonString, ',', '"constructorArgs":"', _bytesToHexString(_deploymentData[i].constructorArgs), '"'
|
|
);
|
|
}
|
|
|
|
// Close the object
|
|
jsonString = string.concat(jsonString, '}');
|
|
|
|
// Add comma between objects, but not after the last one
|
|
if (i < _deploymentData.length - 1) {
|
|
jsonString = string.concat(jsonString, ',');
|
|
}
|
|
}
|
|
|
|
// Close the contracts array and the main object
|
|
jsonString = string.concat(jsonString, ']}');
|
|
|
|
// Write the JSON string to file
|
|
string memory fileName = string.concat('deployments/', vm.toString(block.chainid), '.json');
|
|
vm.writeJson(jsonString, fileName);
|
|
|
|
// intentionally using console for deployment feedback
|
|
console.log('Deployment data saved to %s', fileName);
|
|
}
|
|
|
|
/**
|
|
* @notice Helper function to convert address to string
|
|
* @param addr The address to convert
|
|
* @return The address as a string
|
|
*/
|
|
function _addressToString(address addr) internal pure returns (string memory) {
|
|
return Strings.toHexString(uint160(addr), 20);
|
|
}
|
|
|
|
/**
|
|
* @notice Helper function to convert bytes to hex string
|
|
* @param data The bytes to convert
|
|
* @return hexString The resulting hex string
|
|
*/
|
|
function _bytesToHexString(bytes memory data) internal pure returns (string memory) {
|
|
bytes memory alphabet = '0123456789abcdef';
|
|
bytes memory str = new bytes(2 + data.length * 2);
|
|
str[0] = '0';
|
|
str[1] = 'x';
|
|
|
|
for (uint256 i = 0; i < data.length; i++) {
|
|
str[2 + i * 2] = alphabet[uint8(data[i] >> 4)];
|
|
str[2 + i * 2 + 1] = alphabet[uint8(data[i] & 0x0f)];
|
|
}
|
|
|
|
return string(str);
|
|
}
|
|
|
|
modifier chainId(uint256 _chainId) {
|
|
if (block.chainid != _chainId) revert ChainIdAndRPCMismatch();
|
|
_;
|
|
}
|
|
}
|