Merge branch 'dev' of wonderland:0xbow-io/privacy-pools-core into fix/register-pool-check

This commit is contained in:
moebius
2025-03-14 14:18:00 +01:00
9 changed files with 711 additions and 228 deletions

View File

@@ -17,8 +17,8 @@ defaults:
working-directory: packages/contracts
env:
MAINNET_RPC: ${{ secrets.MAINNET_RPC }}
SEPOLIA_RPC: ${{ secrets.SEPOLIA_RPC }}
ETHEREUM_MAINNET_RPC: ${{ secrets.ETHEREUM_MAINNET_RPC }}
ETHEREUM_SEPOLIA_RPC: ${{ secrets.ETHEREUM_SEPOLIA_RPC }}
jobs:
unit-tests:

View File

@@ -28,8 +28,9 @@ src = 'src/interfaces/'
runs = 1000
[rpc_endpoints]
mainnet = "${MAINNET_RPC}"
sepolia = "${SEPOLIA_RPC}"
mainnet = "${ETHEREUM_MAINNET_RPC}"
sepolia = "${ETHEREUM_SEPOLIA_RPC}"
gnosis = "${GNOSIS_RPC}"
[etherscan]
mainnet = { key = "${ETHERSCAN_API_KEY}" }

View File

@@ -10,8 +10,9 @@
"build": "forge build",
"build:optimized": "FOUNDRY_PROFILE=optimized forge build",
"coverage": "forge coverage --report summary --report lcov --match-path 'test/unit/*'",
"deploy:mainnet": "bash -c 'source .env && forge script Deploy --rpc-url $MAINNET_RPC --account $MAINNET_DEPLOYER_NAME --broadcast --verify --chain mainnet -vvvvv'",
"deploy:protocol:sepolia": "bash -c 'source .env && forge script script/Deploy.s.sol:EthereumSepolia --account $SEPOLIA_DEPLOYER_NAME --verify --rpc-url $SEPOLIA_RPC -vv $0'",
"deploy:mainnet": "bash -c 'source .env && forge script script/Deploy.s.sol:EthereumMainnet --account DEPLOYER --rpc-url $ETHEREUM_MAINNET_RPC --slow -vv $0'",
"deploy:protocol:chiado": "bash -c 'source .env && forge script script/Deploy.s.sol:GnosisChiado --account DEPLOYER --rpc-url $GNOSIS_CHIADO_RPC --slow -vv $0'",
"deploy:protocol:sepolia": "bash -c 'source .env && forge script script/Deploy.s.sol:EthereumSepolia --verify --account DEPLOYER --rpc-url $ETHEREUM_SEPOLIA_RPC --slow -vv $0'",
"lint:check": "yarn lint:sol && forge fmt --check",
"lint:fix": "sort-package-json && forge fmt && yarn lint:sol --fix",
"lint:natspec": "npx @defi-wonderland/natspec-smells --config natspec-smells.config.js",

View File

@@ -0,0 +1,169 @@
// 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';
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 {
// @notice Struct for Pool deployment and configuration
struct PoolConfig {
string symbol;
IERC20 asset;
uint256 minimumDepositAmount;
uint256 vettingFeeBPS;
uint256 maxRelayFeeBPS;
}
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;
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);
// 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]);
}
vm.stopBroadcast();
}
function _deployGroth16Verifiers() private {
// Deploy WithdrawalVerifier using Create2
withdrawalVerifier = CreateX.deployCreate2(
DeployLib.salt(deployer, DeployLib.WITHDRAWAL_VERIFIER_SALT),
abi.encodePacked(type(WithdrawalVerifier).creationCode)
);
console.log('Withdrawal Verifier deployed at: %s', withdrawalVerifier);
// Deploy CommitmentVerifier using Create2
ragequitVerifier = CreateX.deployCreate2(
DeployLib.salt(deployer, DeployLib.RAGEQUIT_VERIFIER_SALT),
abi.encodePacked(type(CommitmentVerifier).creationCode)
);
console.log('Ragequit Verifier deployed at: %s', ragequitVerifier);
}
function _deployEntrypoint() private {
// Deploy implementation at any address
address _impl = address(new Entrypoint());
// 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_SALT),
abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(_impl, _intializationData))
);
entrypoint = Entrypoint(payable(_entrypoint));
console.log('Entrypoint deployed at: %s', address(entrypoint));
}
function _deploySimplePool(PoolConfig memory _config) private {
// Deploy pool with Create2
address _pool = CreateX.deployCreate2(
DeployLib.salt(deployer, DeployLib.SIMPLE_POOL_SALT),
abi.encodePacked(
type(PrivacyPoolSimple).creationCode, abi.encode(address(entrypoint), withdrawalVerifier, ragequitVerifier)
)
);
// Register pool at entrypoint with defined configuration
entrypoint.registerPool(
IERC20(Constants.NATIVE_ASSET),
IPrivacyPool(_pool),
_config.minimumDepositAmount,
_config.vettingFeeBPS,
_config.maxRelayFeeBPS
);
console.log('%s Pool deployed at: %s', _config.symbol, _pool);
}
function _deployComplexPool(PoolConfig memory _config) private {
// Deploy pool with Create2
address _pool = CreateX.deployCreate2(
DeployLib.salt(deployer, DeployLib.COMPLEX_POOL_SALT),
abi.encodePacked(
type(PrivacyPoolComplex).creationCode,
abi.encode(address(entrypoint), withdrawalVerifier, ragequitVerifier, address(_config.asset))
)
);
// Register pool at entrypoint with defined configuration
entrypoint.registerPool(
_config.asset, IPrivacyPool(_pool), _config.minimumDepositAmount, _config.vettingFeeBPS, _config.maxRelayFeeBPS
);
console.log('%s Pool deployed at: %s', _config.symbol, _pool);
}
modifier chainId(uint256 _chainId) {
if (block.chainid != _chainId) revert ChainIdAndRPCMismatch();
_;
}
}

View File

@@ -1,174 +1,19 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.28;
import {ERC20, IERC20} from '@oz/token/ERC20/ERC20.sol';
import {UnsafeUpgrades} from '@upgrades/Upgrades.sol';
import {Script} from 'forge-std/Script.sol';
import {console} from 'forge-std/console.sol';
import {DeployProtocol} from './BaseDeploy.s.sol';
import {IERC20} from '@oz/token/ERC20/ERC20.sol';
import {Constants} from 'contracts/lib/Constants.sol';
import {IPrivacyPool} from 'interfaces/IPrivacyPool.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';
/*///////////////////////////////////////////////////////////////
TEST TOKEN
//////////////////////////////////////////////////////////////*/
contract TestToken is ERC20 {
constructor() ERC20('Test Token', 'TST') {
_mint(msg.sender, 1_000_000 * 10 ** decimals());
}
}
/*///////////////////////////////////////////////////////////////
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 {
// @notice Struct for Pool deployment and configuration
struct PoolConfig {
string symbol;
IERC20 asset;
uint256 minimumDepositAmount;
uint256 vettingFeeBPS;
uint256 maxRelayFeeBPS;
}
// @notice Deployed Entrypoint
Entrypoint public entrypoint;
// @notice Deployed Groth16 Withdrawal Verifier
WithdrawalVerifier public withdrawalVerifier;
// @notice Deployed Groth16 Ragequit Verifier
CommitmentVerifier public ragequitVerifier;
// @notice Initial Entrypoint `ONWER_ROLE`
address public owner;
// @notice Initial Entrypoint `POSTMAN_ROLE`
address public postman;
// @notice Native asset pool configuration
PoolConfig internal _simpleConfig;
// @notice ERC20 pools configurations
PoolConfig[] internal _poolConfigs;
function setUp() public virtual {
owner = vm.envAddress('OWNER_ADDRESS');
postman = vm.envAddress('POSTMAN_ADDRESS');
}
// @dev Must be called with the `--account` flag which acts as the caller
function run() public virtual {
vm.startBroadcast();
// Deploy verifiers
_deployGroth16Verifiers();
// Deploy Entrypoint
_deployEntrypoint();
// Deploy the native asset pool
_deploySimplePool(
_simpleConfig.symbol,
_simpleConfig.minimumDepositAmount,
_simpleConfig.vettingFeeBPS,
_simpleConfig.maxRelayFeeBPS
);
// Deploy the ERC20 pools
// for (uint256 _i; _i < _poolConfigs.length; ++_i) {
// PoolConfig memory _config = _poolConfigs[_i];
// _deployComplexPool(_config.symbol, _config.asset, _config.minimumDepositAmount, _config.vettingFeeBPS, _config.maxRelayFeeBPS);
// }
vm.stopBroadcast();
}
function _deployGroth16Verifiers() private {
withdrawalVerifier = new WithdrawalVerifier();
ragequitVerifier = new CommitmentVerifier();
console.log('Withdrawal Verifier deployed at: %s', address(withdrawalVerifier));
console.log('Ragequit Verifier deployed at: %s', address(ragequitVerifier));
}
function _deployEntrypoint() private {
// Deploy implementation
address _impl = address(new Entrypoint());
// Deploy and initialize proxy
entrypoint = Entrypoint(
payable(UnsafeUpgrades.deployUUPSProxy(_impl, abi.encodeCall(Entrypoint.initialize, (owner, postman))))
);
console.log('Entrypoint deployed at: %s', address(entrypoint));
}
function _deploySimplePool(
string memory _symbol,
uint256 _minimumDepositAmount,
uint256 _vettingFeeBPS,
uint256 _maxRelayFeeBPS
) private {
// Deploy pool
IPrivacyPool _pool = IPrivacyPool(
address(new PrivacyPoolSimple(address(entrypoint), address(withdrawalVerifier), address(ragequitVerifier)))
);
// Register pool at entrypoint with defined configuration
entrypoint.registerPool(
IERC20(Constants.NATIVE_ASSET), _pool, _minimumDepositAmount, _vettingFeeBPS, _maxRelayFeeBPS
);
console.log('%s Pool deployed at: %s', _symbol, address(_pool));
}
function _deployComplexPool(
string memory _symbol,
IERC20 _asset,
uint256 _minimumDepositAmount,
uint256 _vettingFeeBPS,
uint256 _maxRelayFeeBPS
) private {
// Deploy pool
IPrivacyPool _pool = IPrivacyPool(
address(
new PrivacyPoolComplex(
address(entrypoint), address(withdrawalVerifier), address(ragequitVerifier), address(_asset)
)
)
);
// Register pool at entrypoint with defined configuration
entrypoint.registerPool(_asset, _pool, _minimumDepositAmount, _vettingFeeBPS, _maxRelayFeeBPS);
console.log('%s Pool deployed at: %s', _symbol, address(_pool));
}
function _deployTestToken() internal returns (IERC20 _asset) {
_asset = IERC20(address(new TestToken()));
console.log('TestToken deployed at: %s', address(_asset));
}
}
/*///////////////////////////////////////////////////////////////
ETHEREUM SEPOLIA
TESTNETS
//////////////////////////////////////////////////////////////*/
// @notice Protocol configuration for Ethereum Sepolia
contract EthereumSepolia is DeployProtocol {
function setUp() public override {
function setUp() public override chainId(11_155_111) {
// Native asset pool
_simpleConfig = PoolConfig({
_nativePoolConfig = PoolConfig({
symbol: 'ETH',
asset: IERC20(Constants.NATIVE_ASSET),
minimumDepositAmount: 0.001 ether,
@@ -178,61 +23,39 @@ contract EthereumSepolia is DeployProtocol {
super.setUp();
}
}
// Overriding `run` to deploy TestToken before deploying the protocol
function run() public override {
vm.startBroadcast();
contract GnosisChiado is DeployProtocol {
function setUp() public override chainId(10_200) {
// Native asset pool
_nativePoolConfig = PoolConfig({
symbol: 'xDAI',
asset: IERC20(Constants.NATIVE_ASSET),
minimumDepositAmount: 100 ether, // 18 decimals -> 100 xDAI
vettingFeeBPS: 100,
maxRelayFeeBPS: 100
});
// TestToken
// _poolConfigs.push(
// PoolConfig({symbol: 'TST', asset: _deployTestToken(), minimumDepositAmount: 100 ether, vettingFeeBPS: 100}) // 1%
// );
vm.stopBroadcast();
super.run();
super.setUp();
}
}
/*///////////////////////////////////////////////////////////////
ETHEREUM MAINNET
MAINNETS
//////////////////////////////////////////////////////////////*/
// @notice Protocol configuration for Ethereum Mainnet
// TODO: update with actual mainnet configuration
contract EthereumMainnet is DeployProtocol {
function setUp() public override {
function setUp() public override chainId(1) {
// Native asset pool
_simpleConfig = PoolConfig({
_nativePoolConfig = PoolConfig({
symbol: 'ETH',
asset: IERC20(Constants.NATIVE_ASSET),
minimumDepositAmount: 0.001 ether,
vettingFeeBPS: 100,
maxRelayFeeBPS: 100
vettingFeeBPS: 100, // 1%
maxRelayFeeBPS: 100 // 1%
});
// USDT
_poolConfigs.push(
PoolConfig({
symbol: 'USDT',
asset: IERC20(address(0)),
minimumDepositAmount: 100 ether,
vettingFeeBPS: 100,
maxRelayFeeBPS: 100
})
);
// USDC
_poolConfigs.push(
PoolConfig({
symbol: 'USDC',
asset: IERC20(address(0)),
minimumDepositAmount: 100 ether,
vettingFeeBPS: 100,
maxRelayFeeBPS: 100
})
);
super.setUp();
}
}

View File

@@ -0,0 +1,38 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.28;
/**
* @title DeployLib
* @dev A library for deterministic deployment of Privacy Pool contracts and related components
* using CREATE2 via the CreateX contract.
*
* This library provides predefined salt values for deterministic deployments of:
* - Entrypoint (as an UUPS proxy)
* - Simple Privacy Pool (for native assets)
* - Complex Privacy Pool (for ERC20 tokens)
* - Commitment Verifier
* - Withdrawal Verifier
*
* Each component can be deployed with a deterministic address based on these predefined salts.
*/
library DeployLib {
/**
* @dev Predefined salt values for each contract type
* @notice These values ensure deterministic addresses across deployments
*/
bytes11 internal constant ENTRYPOINT_SALT = bytes11(keccak256('Entrypoint1'));
bytes11 internal constant SIMPLE_POOL_SALT = bytes11(keccak256(abi.encodePacked('PrivacyPoolSimple1')));
bytes11 internal constant COMPLEX_POOL_SALT = bytes11(keccak256(abi.encodePacked('PrivacyPoolComplex1')));
bytes11 internal constant WITHDRAWAL_VERIFIER_SALT = bytes11(keccak256(abi.encodePacked('WithdrawalVerifier1')));
bytes11 internal constant RAGEQUIT_VERIFIER_SALT = bytes11(keccak256(abi.encodePacked('RagequitVerifier1')));
/**
* @dev Creates a custom salt for deterministic deployments
* @param _deployer Address of the deployer
* @param _custom Custom salt value
* @return _customSalt The generated salt
*/
function salt(address _deployer, bytes11 _custom) internal pure returns (bytes32 _customSalt) {
return bytes32(abi.encodePacked(_deployer, hex'00', _custom));
}
}

View File

@@ -0,0 +1,153 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.4;
/**
* @title CreateX Factory Interface Definition
* @author pcaversaccio (https://web.archive.org/web/20230921103111/https://pcaversaccio.com/)
* @custom:coauthor Matt Solomon (https://web.archive.org/web/20230921103335/https://mattsolomon.dev/)
*/
interface ICreateX {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* TYPES */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
struct Values {
uint256 constructorAmount;
uint256 initCallAmount;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
event ContractCreation(address indexed newContract, bytes32 indexed salt);
event ContractCreation(address indexed newContract);
event Create3ProxyContractCreation(address indexed newContract, bytes32 indexed salt);
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
error FailedContractCreation(address emitter);
error FailedContractInitialisation(address emitter, bytes revertData);
error InvalidSalt(address emitter);
error InvalidNonceValue(address emitter);
error FailedEtherTransfer(address emitter, bytes revertData);
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CREATE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
function deployCreate(bytes memory initCode) external payable returns (address newContract);
function deployCreateAndInit(
bytes memory initCode,
bytes memory data,
Values memory values,
address refundAddress
) external payable returns (address newContract);
function deployCreateAndInit(
bytes memory initCode,
bytes memory data,
Values memory values
) external payable returns (address newContract);
function deployCreateClone(address implementation, bytes memory data) external payable returns (address proxy);
function computeCreateAddress(address deployer, uint256 nonce) external view returns (address computedAddress);
function computeCreateAddress(uint256 nonce) external view returns (address computedAddress);
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CREATE2 */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
function deployCreate2(bytes32 salt, bytes memory initCode) external payable returns (address newContract);
function deployCreate2(bytes memory initCode) external payable returns (address newContract);
function deployCreate2AndInit(
bytes32 salt,
bytes memory initCode,
bytes memory data,
Values memory values,
address refundAddress
) external payable returns (address newContract);
function deployCreate2AndInit(
bytes32 salt,
bytes memory initCode,
bytes memory data,
Values memory values
) external payable returns (address newContract);
function deployCreate2AndInit(
bytes memory initCode,
bytes memory data,
Values memory values,
address refundAddress
) external payable returns (address newContract);
function deployCreate2AndInit(
bytes memory initCode,
bytes memory data,
Values memory values
) external payable returns (address newContract);
function deployCreate2Clone(
bytes32 salt,
address implementation,
bytes memory data
) external payable returns (address proxy);
function deployCreate2Clone(address implementation, bytes memory data) external payable returns (address proxy);
function computeCreate2Address(
bytes32 salt,
bytes32 initCodeHash,
address deployer
) external pure returns (address computedAddress);
function computeCreate2Address(bytes32 salt, bytes32 initCodeHash) external view returns (address computedAddress);
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CREATE3 */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
function deployCreate3(bytes32 salt, bytes memory initCode) external payable returns (address newContract);
function deployCreate3(bytes memory initCode) external payable returns (address newContract);
function deployCreate3AndInit(
bytes32 salt,
bytes memory initCode,
bytes memory data,
Values memory values,
address refundAddress
) external payable returns (address newContract);
function deployCreate3AndInit(
bytes32 salt,
bytes memory initCode,
bytes memory data,
Values memory values
) external payable returns (address newContract);
function deployCreate3AndInit(
bytes memory initCode,
bytes memory data,
Values memory values,
address refundAddress
) external payable returns (address newContract);
function deployCreate3AndInit(
bytes memory initCode,
bytes memory data,
Values memory values
) external payable returns (address newContract);
function computeCreate3Address(bytes32 salt, address deployer) external pure returns (address computedAddress);
function computeCreate3Address(bytes32 salt) external view returns (address computedAddress);
}

View File

@@ -4,12 +4,15 @@ pragma solidity 0.8.28;
import {Entrypoint, IEntrypoint} from 'contracts/Entrypoint.sol';
import {IPrivacyPool} from 'contracts/PrivacyPool.sol';
import {DeployLib} from 'contracts/lib/DeployLib.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';
import {ERC1967Proxy} from '@oz/proxy/ERC1967/ERC1967Proxy.sol';
import {UnsafeUpgrades} from '@upgrades/Upgrades.sol';
import {IERC20} from '@oz/interfaces/IERC20.sol';
@@ -22,6 +25,7 @@ import {PoseidonT2} from 'poseidon/PoseidonT2.sol';
import {PoseidonT3} from 'poseidon/PoseidonT3.sol';
import {PoseidonT4} from 'poseidon/PoseidonT4.sol';
import {ICreateX} from 'interfaces/external/ICreateX.sol';
import {Constants} from 'test/helper/Constants.sol';
contract IntegrationBase is Test {
@@ -81,14 +85,17 @@ contract IntegrationBase is Test {
uint256 internal constant _FORK_BLOCK = 18_920_905;
// Core protocol contracts
IEntrypoint internal _entrypoint;
IPrivacyPool internal _ethPool;
IPrivacyPool internal _daiPool;
Entrypoint internal _entrypoint;
PrivacyPoolSimple internal _ethPool;
PrivacyPoolComplex internal _daiPool;
// Groth16 Verifiers
CommitmentVerifier internal _commitmentVerifier;
WithdrawalVerifier internal _withdrawalVerifier;
// CreateX Singleton
ICreateX internal constant _CREATEX = ICreateX(0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed);
// Assets
IERC20 internal constant _DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);
IERC20 internal _ETH = IERC20(Constants.NATIVE_ASSET);
@@ -127,43 +134,70 @@ contract IntegrationBase is Test {
SETUP
//////////////////////////////////////////////////////////////*/
//asd
function setUp() public virtual {
vm.createSelectFork(vm.rpcUrl('mainnet'));
vm.startPrank(_OWNER);
// Deploy Groth16 ragequit verifier
_commitmentVerifier = new CommitmentVerifier();
_commitmentVerifier = CommitmentVerifier(
_CREATEX.deployCreate2(
DeployLib.salt(_OWNER, DeployLib.RAGEQUIT_VERIFIER_SALT),
abi.encodePacked(type(CommitmentVerifier).creationCode)
)
);
// Deploy Groth16 withdrawal verifier
_withdrawalVerifier = new WithdrawalVerifier();
_withdrawalVerifier = WithdrawalVerifier(
_CREATEX.deployCreate2(
DeployLib.salt(_OWNER, DeployLib.WITHDRAWAL_VERIFIER_SALT),
abi.encodePacked(type(WithdrawalVerifier).creationCode)
)
);
// Deploy Entrypoint
address _impl = address(new Entrypoint());
_entrypoint = Entrypoint(
payable(UnsafeUpgrades.deployUUPSProxy(_impl, abi.encodeCall(Entrypoint.initialize, (_OWNER, _POSTMAN))))
bytes memory _intializationData = abi.encodeCall(Entrypoint.initialize, (_OWNER, _POSTMAN));
address _entrypointAddr = _CREATEX.deployCreate2(
DeployLib.salt(_OWNER, DeployLib.ENTRYPOINT_SALT),
abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(_impl, _intializationData))
);
_entrypoint = Entrypoint(payable(_entrypointAddr));
// Deploy ETH pool
_ethPool = PrivacyPoolSimple(
_CREATEX.deployCreate2(
DeployLib.salt(_OWNER, DeployLib.SIMPLE_POOL_SALT),
abi.encodePacked(
type(PrivacyPoolSimple).creationCode,
abi.encode(address(_entrypoint), address(_withdrawalVerifier), address(_commitmentVerifier))
)
)
);
// Deploy ETH Pool
_ethPool = IPrivacyPool(
address(new PrivacyPoolSimple(address(_entrypoint), address(_withdrawalVerifier), address(_commitmentVerifier)))
);
// Deploy DAI Pool
_daiPool = IPrivacyPool(
address(
new PrivacyPoolComplex(
address(_entrypoint), address(_withdrawalVerifier), address(_commitmentVerifier), address(_DAI)
// Deploy DAI pool
_daiPool = PrivacyPoolComplex(
_CREATEX.deployCreate2(
DeployLib.salt(_OWNER, DeployLib.COMPLEX_POOL_SALT),
abi.encodePacked(
type(PrivacyPoolComplex).creationCode,
abi.encode(address(_entrypoint), address(_withdrawalVerifier), address(_commitmentVerifier), address(_DAI))
)
)
);
// Register ETH pool
_entrypoint.registerPool(
IERC20(Constants.NATIVE_ASSET), IPrivacyPool(_ethPool), _MIN_DEPOSIT, _VETTING_FEE_BPS, _MAX_RELAY_FEE_BPS
IERC20(Constants.NATIVE_ASSET),
IPrivacyPool(address(_ethPool)),
_MIN_DEPOSIT,
_VETTING_FEE_BPS,
_MAX_RELAY_FEE_BPS
);
// Register DAI pool
_entrypoint.registerPool(_DAI, IPrivacyPool(_daiPool), _MIN_DEPOSIT, _VETTING_FEE_BPS, _MAX_RELAY_FEE_BPS);
_entrypoint.registerPool(_DAI, IPrivacyPool(address(_daiPool)), _MIN_DEPOSIT, _VETTING_FEE_BPS, _MAX_RELAY_FEE_BPS);
vm.stopPrank();
}
@@ -183,7 +217,9 @@ contract IntegrationBase is Test {
}
// Define pool to deposit to
IPrivacyPool _pool = _params.asset == IERC20(Constants.NATIVE_ASSET) ? _ethPool : _daiPool;
IPrivacyPool _pool = _params.asset == IERC20(Constants.NATIVE_ASSET)
? IPrivacyPool(address(_ethPool))
: IPrivacyPool(address(_daiPool));
// Fetch current nonce
uint256 _currentNonce = _pool.nonce();
@@ -248,7 +284,9 @@ contract IntegrationBase is Test {
function _selfWithdraw(WithdrawalParams memory _params) internal returns (Commitment memory _commitment) {
// Define pool to deposit to
IPrivacyPool _pool = _params.commitment.asset == IERC20(Constants.NATIVE_ASSET) ? _ethPool : _daiPool;
IPrivacyPool _pool = _params.commitment.asset == IERC20(Constants.NATIVE_ASSET)
? IPrivacyPool(address(_ethPool))
: IPrivacyPool(address(_daiPool));
// Build `Withdrawal` object for direct withdrawal
IPrivacyPool.Withdrawal memory _withdrawal = IPrivacyPool.Withdrawal({processooor: _params.recipient, data: ''});
@@ -259,7 +297,9 @@ contract IntegrationBase is Test {
function _withdrawThroughRelayer(WithdrawalParams memory _params) internal returns (Commitment memory _commitment) {
// Define pool to deposit to
IPrivacyPool _pool = _params.commitment.asset == IERC20(Constants.NATIVE_ASSET) ? _ethPool : _daiPool;
IPrivacyPool _pool = _params.commitment.asset == IERC20(Constants.NATIVE_ASSET)
? IPrivacyPool(address(_ethPool))
: IPrivacyPool(address(_daiPool));
// Build `Withdrawal` object for relayed withdrawal
IPrivacyPool.Withdrawal memory _withdrawal = IPrivacyPool.Withdrawal({
@@ -357,7 +397,9 @@ contract IntegrationBase is Test {
function _ragequit(address _depositor, Commitment memory _commitment) internal {
// Define pool to ragequit from
IPrivacyPool _pool = _commitment.asset == IERC20(Constants.NATIVE_ASSET) ? _ethPool : _daiPool;
IPrivacyPool _pool = _commitment.asset == IERC20(Constants.NATIVE_ASSET)
? IPrivacyPool(address(_ethPool))
: IPrivacyPool(address(_daiPool));
uint256 _depositorInitialBalance = _balance(_depositor, _commitment.asset);
uint256 _entrypointInitialBalance = _balance(address(_entrypoint), _commitment.asset);

View File

@@ -0,0 +1,256 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.28;
import {ERC1967Proxy} from '@oz/proxy/ERC1967/ERC1967Proxy.sol';
import {DeployLib} from 'contracts/lib/DeployLib.sol';
import {Test} from 'forge-std/Test.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';
/**
* @title IntegrationDeploy
* @notice Integration test for verifying deterministic CREATE2 deployments across multiple chains
* @dev This test ensures that Privacy Pool contracts are deployed to the same addresses
* across different EVM-compatible chains (mainnet, sepolia, gnosis) using CREATE2
*/
contract IntegrationDeploy is Test {
/**
* @notice Structure to hold deployed contract addresses for a specific chain
* @param commitmentVerifier Address of the CommitmentVerifier contract
* @param withdrawalVerifier Address of the WithdrawalVerifier contract
* @param entrypoint Address of the Entrypoint contract
* @param nativePool Address of the PrivacyPoolSimple contract (for native assets)
* @param tokenPool Address of the PrivacyPoolComplex contract (for ERC20 tokens)
*/
struct Contracts {
address commitmentVerifier;
address withdrawalVerifier;
address entrypoint;
address nativePool;
address tokenPool;
}
/**
* @notice Structure to hold chain-specific configuration
* @param id Chain ID
* @param name Chain name (used for forking)
* @param native Native token symbol
* @param token Address of the ERC20 token to use for testing
* @param tokenSymbol Symbol of the ERC20 token
*/
struct ChainConfig {
uint256 id;
string name;
string native;
address token;
string tokenSymbol;
}
/*///////////////////////////////////////////////////////////////
STATE VARIABLES
//////////////////////////////////////////////////////////////*/
/**
* @notice Array of chain configurations to test
*/
ChainConfig[] internal _chains;
/**
* @notice Mapping from chain ID to deployed contract addresses
*/
mapping(uint256 _chainId => Contracts _contracts) internal _contracts;
/**
* @notice Address used for deployment operations
*/
address internal immutable _DEPLOYER = makeAddr('DEPLOYER');
/**
* @notice Mock token address used for testing
*/
address internal _TOKEN = makeAddr('singleton_token');
/**
* @notice CreateX Singleton
*/
ICreateX internal constant _CREATEX = ICreateX(0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed);
/**
* @notice Set up test environment with chain configurations
* @dev Initializes configurations for mainnet, sepolia, and gnosis chains
*/
function setUp() public {
_chains.push(ChainConfig(1, 'mainnet', 'ETH', _TOKEN, 'SYMBOL'));
_chains.push(ChainConfig(11_155_111, 'sepolia', 'ETH', _TOKEN, 'SYMBOL'));
_chains.push(ChainConfig(100, 'gnosis', 'xDAI', _TOKEN, 'SYMBOL'));
}
/**
* @notice Test deterministic CREATE2 deployments across multiple chains
* @dev For each chain:
* 1. Creates a fork of the chain
* 2. Deploys all Privacy Pool contracts using DeployLib
* 3. Stores the deployed addresses
* Then verifies that all contracts are deployed to the same addresses across chains
*/
function test_create2() public virtual {
for (uint256 _i; _i < _chains.length; ++_i) {
ChainConfig memory _chain = _chains[_i];
// Fork the chain
vm.createSelectFork(vm.rpcUrl(_chain.name));
vm.startPrank(_DEPLOYER);
// Deploy all contracts for this chain
_deployContractsForChain(_chain);
vm.stopPrank();
}
// Verify that contract addresses are the same across all chains
_verifyAddressesMatch();
}
/**
* @notice Deploy all contracts for a specific chain
* @param _chain Chain configuration
*/
function _deployContractsForChain(ChainConfig memory _chain) private {
// Deploy verifiers
address _commitmentVerifier = _deployCommitmentVerifier();
address _withdrawalVerifier = _deployWithdrawalVerifier();
// Store verifier addresses
_contracts[_chain.id].commitmentVerifier = _commitmentVerifier;
_contracts[_chain.id].withdrawalVerifier = _withdrawalVerifier;
// Deploy Entrypoint
address _entrypoint = _deployEntrypoint();
_contracts[_chain.id].entrypoint = _entrypoint;
// Deploy pools
_contracts[_chain.id].nativePool = _deployNativePool(_entrypoint, _withdrawalVerifier, _commitmentVerifier);
_contracts[_chain.id].tokenPool =
_deployTokenPool(_entrypoint, _withdrawalVerifier, _commitmentVerifier, _chain.token);
}
/**
* @notice Deploy CommitmentVerifier contract
* @return Address of the deployed CommitmentVerifier
*/
function _deployCommitmentVerifier() private returns (address) {
return _CREATEX.deployCreate2(
DeployLib.salt(_DEPLOYER, DeployLib.RAGEQUIT_VERIFIER_SALT),
abi.encodePacked(type(CommitmentVerifier).creationCode)
);
}
/**
* @notice Deploy WithdrawalVerifier contract
* @return Address of the deployed WithdrawalVerifier
*/
function _deployWithdrawalVerifier() private returns (address) {
return _CREATEX.deployCreate2(
DeployLib.salt(_DEPLOYER, DeployLib.WITHDRAWAL_VERIFIER_SALT),
abi.encodePacked(type(WithdrawalVerifier).creationCode)
);
}
/**
* @notice Deploy Entrypoint contract
* @return Address of the deployed Entrypoint
*/
function _deployEntrypoint() private returns (address) {
address _owner = makeAddr('OWNER');
address _postman = makeAddr('POSTMAN');
address _impl = address(new Entrypoint());
bytes memory _intializationData = abi.encodeCall(Entrypoint.initialize, (_owner, _postman));
return _CREATEX.deployCreate2(
DeployLib.salt(_DEPLOYER, DeployLib.ENTRYPOINT_SALT),
abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(_impl, _intializationData))
);
}
/**
* @notice Deploy PrivacyPoolSimple contract for native assets
* @param _entrypoint Address of the Entrypoint contract
* @param _withdrawalVerifier Address of the WithdrawalVerifier contract
* @param _commitmentVerifier Address of the CommitmentVerifier contract
* @return Address of the deployed PrivacyPoolSimple
*/
function _deployNativePool(
address _entrypoint,
address _withdrawalVerifier,
address _commitmentVerifier
) private returns (address) {
return _CREATEX.deployCreate2(
DeployLib.salt(_DEPLOYER, DeployLib.SIMPLE_POOL_SALT),
abi.encodePacked(
type(PrivacyPoolSimple).creationCode, abi.encode(_entrypoint, _withdrawalVerifier, _commitmentVerifier)
)
);
}
/**
* @notice Deploy PrivacyPoolComplex contract for ERC20 tokens
* @param _entrypoint Address of the Entrypoint contract
* @param _withdrawalVerifier Address of the WithdrawalVerifier contract
* @param _commitmentVerifier Address of the CommitmentVerifier contract
* @param _token Address of the ERC20 token
* @return Address of the deployed PrivacyPoolComplex
*/
function _deployTokenPool(
address _entrypoint,
address _withdrawalVerifier,
address _commitmentVerifier,
address _token
) private returns (address) {
return _CREATEX.deployCreate2(
DeployLib.salt(_DEPLOYER, DeployLib.COMPLEX_POOL_SALT),
abi.encodePacked(
type(PrivacyPoolComplex).creationCode, abi.encode(_entrypoint, _withdrawalVerifier, _commitmentVerifier, _token)
)
);
}
/**
* @notice Verify that contract addresses match across all chains
*/
function _verifyAddressesMatch() private view {
assertTrue(
_contracts[1].commitmentVerifier == _contracts[11_155_111].commitmentVerifier
&& _contracts[11_155_111].commitmentVerifier == _contracts[100].commitmentVerifier,
"Commitment verifier addresses don't match"
);
assertTrue(
_contracts[1].withdrawalVerifier == _contracts[11_155_111].withdrawalVerifier
&& _contracts[11_155_111].withdrawalVerifier == _contracts[100].withdrawalVerifier,
"Withdrawal verifier addresses don't match"
);
assertTrue(
_contracts[1].entrypoint == _contracts[11_155_111].entrypoint
&& _contracts[11_155_111].entrypoint == _contracts[100].entrypoint,
"Entrypoint addresses don't match"
);
assertTrue(
_contracts[1].nativePool == _contracts[11_155_111].nativePool
&& _contracts[11_155_111].nativePool == _contracts[100].nativePool,
"Native pool addresses don't match"
);
assertTrue(
_contracts[1].tokenPool == _contracts[11_155_111].tokenPool
&& _contracts[11_155_111].tokenPool == _contracts[100].tokenPool,
"Complex pool addresses don't match"
);
}
}