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
136 changes: 26 additions & 110 deletions contracts/finance/VestingWalletConfidentialFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,19 @@ import {VestingWalletCliffConfidential} from "./VestingWalletCliffConfidential.s
*/
abstract contract VestingWalletConfidentialFactory {
struct VestingPlan {
address beneficiary;
externalEuint64 encryptedAmount;
uint48 startTimestamp;
uint48 durationSeconds;
uint48 cliffSeconds;
bytes initArgs;
}

address private immutable _vestingImplementation;

event VestingWalletConfidentialFunded(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filtering events by beneficiary would be useful for developers.

Copy link
Contributor Author

@arr00 arr00 Aug 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree but that is part of the initialization. Do you propose passing it separately as well just for the sake of indexing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think integrators can look at the initialization code

address indexed vestingWalletConfidential,
address indexed beneficiary,
address indexed confidentialFungibleToken,
euint64 encryptedAmount,
uint48 startTimestamp,
uint48 durationSeconds,
uint48 cliffSeconds,
address executor
);
event VestingWalletConfidentialCreated(
address indexed vestingWalletConfidential,
address indexed beneficiary,
uint48 startTimestamp,
uint48 durationSeconds,
uint48 cliffSeconds,
address indexed executor
euint64 transferredAmount,
bytes initArgs
);
event VestingWalletConfidentialCreated(address indexed vestingWalletConfidential, bytes initArgs);

constructor() {
_vestingImplementation = _deployVestingWalletImplementation();
Expand All @@ -58,50 +44,28 @@ abstract contract VestingWalletConfidentialFactory {
function batchFundVestingWalletConfidential(
address confidentialFungibleToken,
VestingPlan[] calldata vestingPlans,
address executor,
bytes calldata inputProof
) public virtual {
uint256 vestingPlansLength = vestingPlans.length;
for (uint256 i = 0; i < vestingPlansLength; i++) {
VestingPlan memory vestingPlan = vestingPlans[i];
require(
vestingPlan.cliffSeconds <= vestingPlan.durationSeconds,
VestingWalletCliffConfidential.VestingWalletCliffConfidentialInvalidCliffDuration(
vestingPlan.cliffSeconds,
vestingPlan.durationSeconds
)
);
_validateVestingWalletInitArgs(vestingPlan.initArgs);

require(vestingPlan.beneficiary != address(0), OwnableUpgradeable.OwnableInvalidOwner(address(0)));
address vestingWalletAddress = predictVestingWalletConfidential(
vestingPlan.beneficiary,
vestingPlan.startTimestamp,
vestingPlan.durationSeconds,
vestingPlan.cliffSeconds,
executor
);
address vestingWalletAddress = predictVestingWalletConfidential(vestingPlan.initArgs);

euint64 transferredAmount;
{
// avoiding stack too deep with scope
euint64 encryptedAmount = FHE.fromExternal(vestingPlan.encryptedAmount, inputProof);
FHE.allowTransient(encryptedAmount, confidentialFungibleToken);
transferredAmount = IConfidentialFungibleToken(confidentialFungibleToken).confidentialTransferFrom(
msg.sender,
vestingWalletAddress,
encryptedAmount
);
}
euint64 encryptedAmount = FHE.fromExternal(vestingPlan.encryptedAmount, inputProof);
FHE.allowTransient(encryptedAmount, confidentialFungibleToken);
euint64 transferredAmount = IConfidentialFungibleToken(confidentialFungibleToken).confidentialTransferFrom(
msg.sender,
vestingWalletAddress,
encryptedAmount
);

emit VestingWalletConfidentialFunded(
vestingWalletAddress,
vestingPlan.beneficiary,
confidentialFungibleToken,
transferredAmount,
vestingPlan.startTimestamp,
vestingPlan.durationSeconds,
vestingPlan.cliffSeconds,
executor
vestingPlan.initArgs
);
}
}
Expand All @@ -111,75 +75,33 @@ abstract contract VestingWalletConfidentialFactory {
*
* Emits a {VestingWalletConfidentialCreated}.
*/
function createVestingWalletConfidential(
address beneficiary,
uint48 startTimestamp,
uint48 durationSeconds,
uint48 cliffSeconds,
address executor
) public virtual returns (address) {
function createVestingWalletConfidential(bytes calldata initArgs) public virtual returns (address) {
// Will revert if clone already created
address vestingWalletConfidentialAddress = Clones.cloneDeterministic(
_vestingImplementation,
_getCreate2VestingWalletConfidentialSalt(
beneficiary,
startTimestamp,
durationSeconds,
cliffSeconds,
executor
)
);
_initializeVestingWallet(
vestingWalletConfidentialAddress,
beneficiary,
startTimestamp,
durationSeconds,
cliffSeconds,
executor
);
emit VestingWalletConfidentialCreated(
vestingWalletConfidentialAddress,
beneficiary,
startTimestamp,
durationSeconds,
cliffSeconds,
executor
_getCreate2VestingWalletConfidentialSalt(initArgs)
);
_initializeVestingWallet(vestingWalletConfidentialAddress, initArgs);
emit VestingWalletConfidentialCreated(vestingWalletConfidentialAddress, initArgs);
return vestingWalletConfidentialAddress;
}

/**
* @dev Predicts the deterministic address for a confidential vesting wallet.
*/
function predictVestingWalletConfidential(
address beneficiary,
uint48 startTimestamp,
uint48 durationSeconds,
uint48 cliffSeconds,
address executor
) public view virtual returns (address) {
function predictVestingWalletConfidential(bytes memory initArgs) public view virtual returns (address) {
return
Clones.predictDeterministicAddress(
_vestingImplementation,
_getCreate2VestingWalletConfidentialSalt(
beneficiary,
startTimestamp,
durationSeconds,
cliffSeconds,
executor
)
_getCreate2VestingWalletConfidentialSalt(initArgs)
);
}

/// @dev Virtual function that must be implemented to validate the initArgs bytes.
function _validateVestingWalletInitArgs(bytes memory initArgs) internal virtual;

/// @dev Virtual function that must be implemented to initialize the vesting wallet at `vestingWalletAddress`.
function _initializeVestingWallet(
address vestingWalletAddress,
address beneficiary,
uint48 startTimestamp,
uint48 durationSeconds,
uint48 cliffSeconds,
address executor
) internal virtual;
function _initializeVestingWallet(address vestingWalletAddress, bytes calldata initArgs) internal virtual;

/**
* @dev Internal function that is called once to deploy the vesting wallet implementation.
Expand All @@ -191,13 +113,7 @@ abstract contract VestingWalletConfidentialFactory {
/**
* @dev Gets create2 salt for a confidential vesting wallet.
*/
function _getCreate2VestingWalletConfidentialSalt(
address beneficiary,
uint48 startTimestamp,
uint48 durationSeconds,
uint48 cliffSeconds,
address executor
) internal pure virtual returns (bytes32) {
return keccak256(abi.encodePacked(beneficiary, startTimestamp, durationSeconds, cliffSeconds, executor));
function _getCreate2VestingWalletConfidentialSalt(bytes memory initArgs) internal pure virtual returns (bytes32) {
return keccak256(initArgs);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,29 @@ abstract contract VestingWalletConfidentialFactoryMock is VestingWalletConfident
return address(new VestingWalletCliffExecutorConfidential());
}

function _initializeVestingWallet(
address vestingWalletAddress,
address beneficiary,
uint48 startTimestamp,
uint48 durationSeconds,
uint48 cliffSeconds,
address executor
) internal virtual override {
function _validateVestingWalletInitArgs(bytes memory initArgs) internal virtual override {
// solhint-disable no-unused-vars
(
address beneficiary,
uint48 startTimestamp,
uint48 durationSeconds,
uint48 cliffSeconds,
address executor
) = abi.decode(initArgs, (address, uint48, uint48, uint48, address));

require(cliffSeconds <= durationSeconds);
require(beneficiary != address(0));
}

function _initializeVestingWallet(address vestingWalletAddress, bytes calldata initArgs) internal virtual override {
(
address beneficiary,
uint48 startTimestamp,
uint48 durationSeconds,
uint48 cliffSeconds,
address executor
) = abi.decode(initArgs, (address, uint48, uint48, uint48, address));

VestingWalletCliffExecutorConfidential(vestingWalletAddress).initialize(
beneficiary,
startTimestamp,
Expand Down
Loading