Skip to content
Open
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
22 changes: 21 additions & 1 deletion src/ZkMinterTriggerV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ contract ZkMinterTriggerV1 is ZkMinterV1 {
/// @notice The immutable address where tokens are sent when recovered.
address public immutable RECOVERY_ADDRESS;

/// @notice Struct describing an ERC20 approval that should be configured during construction.
struct Approval {
address token;
address spender;
uint256 amount;
}

/// @notice Emitted when trigger is executed.
event TriggerExecuted(address indexed caller);

Expand Down Expand Up @@ -55,20 +62,25 @@ contract ZkMinterTriggerV1 is ZkMinterV1 {
/// @notice Error for when the ETH transfer to the recovery address fails.
error ZkMinterTriggerV1__RecoveryEthTransferFailed();

/// @notice Error for when an approval contains invalid data.
error ZkMinterTriggerV1__InvalidApproval(uint256 index, address token, address spender);

/// @notice Initializes the trigger contract with mintable, admin, and trigger parameters.
/// @param _mintable A contract used as a target when calling mint.
/// @param _admin The address that will have admin privileges.
/// @param _targetAddresses The target contracts to call.
/// @param _calldatas The call data for the functions.
/// @param _values The ETH values to send with the calls.
/// @param _recoveryAddress The immutable address where minted tokens can be sent.
/// @param _approvals Optional approvals to configure for downstream spenders.
constructor(
IMintable _mintable,
address _admin,
address[] memory _targetAddresses,
bytes[] memory _calldatas,
uint256[] memory _values,
address _recoveryAddress
address _recoveryAddress,
Approval[] memory _approvals
) {
if (_admin == address(0)) {
revert ZkMinterTriggerV1__InvalidAdmin();
Expand All @@ -90,6 +102,14 @@ contract ZkMinterTriggerV1 is ZkMinterV1 {
calldatas = _calldatas;
values = _values;
RECOVERY_ADDRESS = _recoveryAddress;

for (uint256 _i = 0; _i < _approvals.length; _i++) {
Approval memory _approval = _approvals[_i];
if (_approval.token == address(0) || _approval.spender == address(0)) {
revert ZkMinterTriggerV1__InvalidApproval(_i, _approval.token, _approval.spender);
}
IERC20(_approval.token).safeApprove(_approval.spender, _approval.amount);
}
}

/// @notice Mints tokens to this contract address.
Expand Down
36 changes: 25 additions & 11 deletions src/ZkMinterTriggerV1Factory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {IMintable} from "src/interfaces/IMintable.sol";
/// @notice Factory contract to deploy `ZkMinterTriggerV1` contracts using CREATE2. This factory enables
/// deterministic deployment of trigger-based minter contracts with predictable addresses. The factory offers two
/// deployment interfaces:
/// - `createMinter(IMintable,address,address[],bytes[],uint256[],address,uint256)` for strongly typed params
/// - `createMinter(IMintable,address,address[],bytes[],uint256[],address,ZkMinterTriggerV1.Approval[],uint256)` for
/// strongly typed params
/// - `createMinter(IMintable,bytes)` for unified factory compatibility
/// @custom:security-contact security@matterlabs.dev
contract ZkMinterTriggerV1Factory is IZkMinterV1Factory {
Expand All @@ -34,14 +35,16 @@ contract ZkMinterTriggerV1Factory is IZkMinterV1Factory {
/// @param calldatas Calldata forwarded to each target contract.
/// @param values ETH values forwarded to each target contract.
/// @param recovery The immutable recovery address for the trigger.
/// @param approvals Optional approvals configured during construction.
event MinterTriggerCreated(
address indexed minterTrigger,
IMintable mintable,
address admin,
address[] targets,
bytes[] calldatas,
uint256[] values,
address recovery
address recovery,
ZkMinterTriggerV1.Approval[] approvals
);

/* ///////////////////////////////////////////////////////////////
Expand All @@ -65,6 +68,7 @@ contract ZkMinterTriggerV1Factory is IZkMinterV1Factory {
/// @param _calldatas Calldata forwarded to each target contract.
/// @param _values ETH values forwarded with each call.
/// @param _recoveryAddress Immutable address that receives recovered funds.
/// @param _approvals Optional approvals configured during the trigger deployment.
/// @param _saltNonce A user-provided nonce for salt calculation.
/// @return _minterTriggerAddress The address of the newly deployed `ZkMinterTriggerV1`.
function createMinter(
Expand All @@ -74,17 +78,19 @@ contract ZkMinterTriggerV1Factory is IZkMinterV1Factory {
bytes[] memory _calldatas,
uint256[] memory _values,
address _recoveryAddress,
ZkMinterTriggerV1.Approval[] memory _approvals,
uint256 _saltNonce
) external returns (address _minterTriggerAddress) {
_minterTriggerAddress =
_createMinter(_mintable, _admin, _targets, _calldatas, _values, _recoveryAddress, _saltNonce);
_minterTriggerAddress = _createMinter(
_mintable, _admin, _targets, _calldatas, _values, _recoveryAddress, _approvals, _saltNonce
);
}

/// @notice Deploys a new `ZkMinterTriggerV1` contract using `CREATE2`. This overload accepts encoded args so it can
/// be composed inside higher level factories.
/// @param _mintable A contract used as a target when calling mint.
/// @param _args Encoded args: `(address admin, address[] targets, bytes[] calldatas, uint256[] values,
/// address recoveryAddress, uint256 saltNonce)`.
/// address recoveryAddress, ZkMinterTriggerV1.Approval[] approvals, uint256 saltNonce)`.
/// @return The address of the newly deployed `ZkMinterTriggerV1`.
function createMinter(IMintable _mintable, bytes memory _args) external returns (address) {
(
Expand All @@ -93,10 +99,11 @@ contract ZkMinterTriggerV1Factory is IZkMinterV1Factory {
bytes[] memory _calldatas,
uint256[] memory _values,
address _recoveryAddress,
ZkMinterTriggerV1.Approval[] memory _approvals,
uint256 _saltNonce
) = abi.decode(_args, (address, address[], bytes[], uint256[], address, uint256));
) = abi.decode(_args, (address, address[], bytes[], uint256[], address, ZkMinterTriggerV1.Approval[], uint256));

return _createMinter(_mintable, _admin, _targets, _calldatas, _values, _recoveryAddress, _saltNonce);
return _createMinter(_mintable, _admin, _targets, _calldatas, _values, _recoveryAddress, _approvals, _saltNonce);
}

/// @notice Computes the address of a `ZkMinterTriggerV1` deployed via this factory.
Expand All @@ -106,6 +113,7 @@ contract ZkMinterTriggerV1Factory is IZkMinterV1Factory {
/// @param _calldatas Calldata forwarded to each target contract.
/// @param _values ETH values forwarded with each call.
/// @param _recoveryAddress Immutable address that receives recovered funds.
/// @param _approvals Optional approvals configured during the trigger deployment.
/// @param _saltNonce The nonce used for salt calculation.
/// @return _minterTriggerAddress The address of the `ZkMinterTriggerV1`.
function getMinter(
Expand All @@ -115,14 +123,15 @@ contract ZkMinterTriggerV1Factory is IZkMinterV1Factory {
bytes[] memory _calldatas,
uint256[] memory _values,
address _recoveryAddress,
ZkMinterTriggerV1.Approval[] memory _approvals,
uint256 _saltNonce
) external view returns (address _minterTriggerAddress) {
bytes32 _salt = _calculateSalt(_saltNonce);
_minterTriggerAddress = L2ContractHelper.computeCreate2Address(
address(this),
_salt,
BYTECODE_HASH,
keccak256(abi.encode(_mintable, _admin, _targets, _calldatas, _values, _recoveryAddress))
keccak256(abi.encode(_mintable, _admin, _targets, _calldatas, _values, _recoveryAddress, _approvals))
);
}

Expand All @@ -144,6 +153,7 @@ contract ZkMinterTriggerV1Factory is IZkMinterV1Factory {
/// @param _calldatas Calldata forwarded to each target contract.
/// @param _values ETH values forwarded with each call.
/// @param _recoveryAddress Immutable address that receives recovered funds.
/// @param _approvals Optional approvals configured during the trigger deployment.
/// @param _saltNonce A user-provided nonce for salt calculation.
/// @return _minterTriggerAddress The address of the newly deployed `ZkMinterTriggerV1`.
function _createMinter(
Expand All @@ -153,14 +163,18 @@ contract ZkMinterTriggerV1Factory is IZkMinterV1Factory {
bytes[] memory _calldatas,
uint256[] memory _values,
address _recoveryAddress,
ZkMinterTriggerV1.Approval[] memory _approvals,
uint256 _saltNonce
) internal returns (address _minterTriggerAddress) {
bytes32 _salt = _calculateSalt(_saltNonce);

ZkMinterTriggerV1 _instance =
new ZkMinterTriggerV1{salt: _salt}(_mintable, _admin, _targets, _calldatas, _values, _recoveryAddress);
ZkMinterTriggerV1 _instance = new ZkMinterTriggerV1{salt: _salt}(
_mintable, _admin, _targets, _calldatas, _values, _recoveryAddress, _approvals
);
_minterTriggerAddress = address(_instance);

emit MinterTriggerCreated(_minterTriggerAddress, _mintable, _admin, _targets, _calldatas, _values, _recoveryAddress);
emit MinterTriggerCreated(
_minterTriggerAddress, _mintable, _admin, _targets, _calldatas, _values, _recoveryAddress, _approvals
);
}
}
2 changes: 1 addition & 1 deletion test/ZkMinterTriggerV1.integration.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ contract ZkMinterTriggerV1Integration is ZkMinterTriggerV1Test {
_values[1] = _ethValue;

ZkMinterTriggerV1 _multiTrigger =
new ZkMinterTriggerV1(mintable, admin, _targets, _calldatas, _values, recoveryAddress);
new ZkMinterTriggerV1(mintable, admin, _targets, _calldatas, _values, recoveryAddress, _emptyApprovals());

// Configure roles
_grantMinterRole(cappedMinter, cappedMinterAdmin, address(_multiTrigger));
Expand Down
Loading
Loading