Finance

This directory includes primitives for on-chain confidential financial systems:

For convenience, this directory also includes:

Contracts

VestingWalletConfidential

import "@openzeppelin/confidential-contracts/finance/VestingWalletConfidential.sol";

A vesting wallet is an ownable contract that can receive ERC7984 tokens, and release these assets to the wallet owner, also referred to as "beneficiary", according to a vesting schedule.

Any assets transferred to this contract will follow the vesting schedule as if they were locked from the beginning. Consequently, if the vesting has already started, any amount of tokens sent to this contract will (at least partly) be immediately releasable.

By setting the duration to 0, one can configure this contract to behave like an asset timelock that holds tokens for a beneficiary until a specified time.

Since the wallet is Ownable, and ownership can be transferred, it is possible to sell unvested tokens.
When using this contract with any token whose balance is adjusted automatically (i.e. a rebase token), make sure to account for the supply/balance adjustment in the vesting schedule to ensure the vested amount is as intended.

Confidential vesting wallet contracts can be deployed (as clones) using the VestingWalletConfidentialFactory.

Functions
  • start()

  • duration()

  • end()

  • released(token)

  • releasable(token)

  • release(token)

  • vestedAmount(token, timestamp)

  • __VestingWalletConfidential_init(beneficiary, startTimestamp, durationSeconds)

  • __VestingWalletConfidential_init_unchained(startTimestamp, durationSeconds)

  • _vestingSchedule(totalAllocation, timestamp)

ReentrancyGuardTransient
  • _reentrancyGuardEntered()

  • _reentrancyGuardStorageSlot()

OwnableUpgradeable
  • __Ownable_init(initialOwner)

  • __Ownable_init_unchained(initialOwner)

  • owner()

  • _checkOwner()

  • renounceOwnership()

  • transferOwnership(newOwner)

  • _transferOwnership(newOwner)

ContextUpgradeable
  • __Context_init()

  • __Context_init_unchained()

  • _msgSender()

  • _msgData()

  • _contextSuffixLength()

Initializable
  • _checkInitializing()

  • _disableInitializers()

  • _getInitializedVersion()

  • _isInitializing()

  • _initializableStorageSlot()

Events
  • VestingWalletConfidentialTokenReleased(token, amount)

OwnableUpgradeable
  • OwnershipTransferred(previousOwner, newOwner)

Initializable
  • Initialized(version)

Errors
ReentrancyGuardTransient
  • ReentrancyGuardReentrantCall()

OwnableUpgradeable
  • OwnableUnauthorizedAccount(account)

  • OwnableInvalidOwner(owner)

Initializable
  • InvalidInitialization()

  • NotInitializing()

start() → uint64 public

Timestamp at which the vesting starts.

duration() → uint64 public

Duration of the vesting in seconds.

end() → uint64 public

Timestamp at which the vesting ends.

released(address token) → euint128 public

Amount of token already released

releasable(address token) → euint64 public

Getter for the amount of releasable token tokens. token should be the address of an IERC7984 contract.

release(address token) public

Release the tokens that have already vested.

vestedAmount(address token, uint48 timestamp) → euint128 public

Calculates the amount of tokens that have been vested at the given timestamp. Default implementation is a linear vesting curve.

__VestingWalletConfidential_init(address beneficiary, uint48 startTimestamp, uint48 durationSeconds) internal

Initializes the vesting wallet for a given beneficiary with a start time of startTimestamp and an end time of startTimestamp + durationSeconds.

__VestingWalletConfidential_init_unchained(uint48 startTimestamp, uint48 durationSeconds) internal

_vestingSchedule(euint128 totalAllocation, uint48 timestamp) → euint128 internal

This returns the amount vested, as a function of time, for an asset given its total historical allocation.

VestingWalletConfidentialTokenReleased(address indexed token, euint64 amount) event

Emitted when releasable vested tokens are released.

VestingWalletCliffConfidential

import "@openzeppelin/confidential-contracts/finance/VestingWalletCliffConfidential.sol";

An extension of VestingWalletConfidential that adds a cliff to the vesting schedule. The cliff is cliffSeconds long and starts at the vesting start timestamp (see VestingWalletConfidential).

Functions
  • cliff()

  • __VestingWalletCliffConfidential_init(beneficiary, startTimestamp, durationSeconds, cliffSeconds)

  • __VestingWalletCliffConfidential_init_unchained(cliffSeconds)

  • _vestingSchedule(totalAllocation, timestamp)

VestingWalletConfidential
  • start()

  • duration()

  • end()

  • released(token)

  • releasable(token)

  • release(token)

  • vestedAmount(token, timestamp)

  • __VestingWalletConfidential_init(beneficiary, startTimestamp, durationSeconds)

  • __VestingWalletConfidential_init_unchained(startTimestamp, durationSeconds)

ReentrancyGuardTransient
  • _reentrancyGuardEntered()

  • _reentrancyGuardStorageSlot()

OwnableUpgradeable
  • __Ownable_init(initialOwner)

  • __Ownable_init_unchained(initialOwner)

  • owner()

  • _checkOwner()

  • renounceOwnership()

  • transferOwnership(newOwner)

  • _transferOwnership(newOwner)

ContextUpgradeable
  • __Context_init()

  • __Context_init_unchained()

  • _msgSender()

  • _msgData()

  • _contextSuffixLength()

Initializable
  • _checkInitializing()

  • _disableInitializers()

  • _getInitializedVersion()

  • _isInitializing()

  • _initializableStorageSlot()

Events
VestingWalletConfidential
  • VestingWalletConfidentialTokenReleased(token, amount)

OwnableUpgradeable
  • OwnershipTransferred(previousOwner, newOwner)

Initializable
  • Initialized(version)

Errors
  • VestingWalletCliffConfidentialInvalidCliffDuration(cliffSeconds, durationSeconds)

ReentrancyGuardTransient
  • ReentrancyGuardReentrantCall()

OwnableUpgradeable
  • OwnableUnauthorizedAccount(account)

  • OwnableInvalidOwner(owner)

Initializable
  • InvalidInitialization()

  • NotInitializing()

cliff() → uint64 public

The timestamp at which the cliff ends.

__VestingWalletCliffConfidential_init(address beneficiary, uint48 startTimestamp, uint48 durationSeconds, uint48 cliffSeconds) internal

Set the duration of the cliff, in seconds. The cliff starts at the vesting start timestamp (see VestingWalletConfidential.start) and ends cliffSeconds later.

__VestingWalletCliffConfidential_init_unchained(uint48 cliffSeconds) internal

_vestingSchedule(euint128 totalAllocation, uint48 timestamp) → euint128 internal

This function returns the amount vested, as a function of time, for an asset given its total historical allocation. Returns 0 if the cliff timestamp is not met.

The cliff not only makes the schedule return 0, but it also ignores every possible side effect from calling the inherited implementation (i.e. super._vestingSchedule). Carefully consider this caveat if the overridden implementation of this function has any (e.g. writing to memory or reverting).

VestingWalletCliffConfidentialInvalidCliffDuration(uint64 cliffSeconds, uint64 durationSeconds) error

The specified cliff duration is larger than the vesting duration.

VestingWalletConfidentialFactory

import "@openzeppelin/confidential-contracts/finance/VestingWalletConfidentialFactory.sol";

A factory which enables batch funding of vesting wallets.

The _deployVestingWalletImplementation, _initializeVestingWallet, and _validateVestingWalletInitArgs functions remain unimplemented to allow for custom implementations of the vesting wallet to be used.

Functions
  • constructor()

  • batchFundVestingWalletConfidential(token, vestingPlans, inputProof)

  • createVestingWalletConfidential(initArgs)

  • predictVestingWalletConfidential(initArgs)

  • _validateVestingWalletInitArgs(initArgs)

  • _initializeVestingWallet(vestingWalletAddress, initArgs)

  • _deployVestingWalletImplementation()

  • _getCreate2VestingWalletConfidentialSalt(initArgs)

Events
  • VestingWalletConfidentialFunded(vestingWalletConfidential, token, transferredAmount, initArgs)

  • VestingWalletConfidentialCreated(vestingWalletConfidential, initArgs)

constructor() internal

batchFundVestingWalletConfidential(address token, struct VestingWalletConfidentialFactory.VestingPlan[] vestingPlans, bytes inputProof) public

Batches the funding of multiple confidential vesting wallets.

Funds are sent to deterministic wallet addresses. Wallets can be created either before or after this operation.

Emits a VestingWalletConfidentialFunded event for each funded vesting plan.

createVestingWalletConfidential(bytes initArgs) → address public

Creates a confidential vesting wallet.

predictVestingWalletConfidential(bytes initArgs) → address public

Predicts the deterministic address for a confidential vesting wallet.

_validateVestingWalletInitArgs(bytes initArgs) internal

Virtual function that must be implemented to validate the initArgs bytes.

_initializeVestingWallet(address vestingWalletAddress, bytes initArgs) internal

Virtual function that must be implemented to initialize the vesting wallet at vestingWalletAddress.

_deployVestingWalletImplementation() → address internal

Internal function that is called once to deploy the vesting wallet implementation.

Vesting wallet clones will be initialized by calls to the _initializeVestingWallet function.

_getCreate2VestingWalletConfidentialSalt(bytes initArgs) → bytes32 internal

Gets create2 salt for a confidential vesting wallet.

VestingWalletConfidentialFunded(address indexed vestingWalletConfidential, address indexed token, euint64 transferredAmount, bytes initArgs) event

Emitted for each vesting wallet funded within a batch.

VestingWalletConfidentialCreated(address indexed vestingWalletConfidential, bytes initArgs) event

Emitted when a vesting wallet is deployed.

BatcherConfidential

import "@openzeppelin/confidential-contracts/finance/BatcherConfidential.sol";

BatcherConfidential is a batching primitive that enables routing between two ERC7984ERC20Wrapper contracts via a non-confidential route. Users deposit fromToken into the batcher and receive toToken in exchange. Deposits are made by using ERC7984 transfer and call functions such as ERC7984.confidentialTransferAndCall.

Developers must implement the virtual function _executeRoute to perform the batch’s route. This function is called once the batch deposits are unwrapped into the underlying tokens. The function should swap the underlying fromToken for underlying toToken. If an issue is encountered, the function should return {ExecuteOutcome.Cancel} to cancel the batch.

Developers must also implement the virtual function routeDescription to provide a human readable description of the batch’s route.

The batcher could be used to maintain confidentiality of deposits—​by default there are no confidentiality guarantees. If desired, developers should consider restricting certain functions to increase confidentiality.
The toToken and fromToken must be carefully inspected to ensure proper capacity is maintained. If toToken or fromToken are filled—​resulting in denial of service—​batches could get bricked. The batcher would be unable to wrap underlying tokens into toToken. Further, if fromToken is also filled, cancellation would also fail on rewrap.
Functions
  • constructor(fromToken_, toToken_)

  • claim(batchId)

  • quit(batchId)

  • dispatchBatch()

  • dispatchBatchCallback(batchId, unwrapAmountCleartext, decryptionProof)

  • onConfidentialTransferReceived(, from, amount, )

  • fromToken()

  • toToken()

  • currentBatchId()

  • unwrapAmount(batchId)

  • totalDeposits(batchId)

  • deposits(batchId, account)

  • exchangeRate(batchId)

  • exchangeRateDecimals()

  • routeDescription()

  • batchState(batchId)

  • _join(to, amount)

  • _executeRoute(batchId, amount)

  • _validateStateBitmap(batchId, allowedStates)

  • _calculateUnwrapAmount(requestedUnwrapAmount)

  • _getAndIncreaseBatchId()

  • _encodeStateBitmap(batchState_)

ReentrancyGuardTransient
  • _reentrancyGuardEntered()

  • _reentrancyGuardStorageSlot()

Events
  • BatchDispatched(batchId)

  • BatchCanceled(batchId)

  • BatchFinalized(batchId, exchangeRate)

  • Joined(batchId, account, amount)

  • Claimed(batchId, account, amount)

  • Quit(batchId, account, amount)

Errors
  • BatchNonexistent(batchId)

  • BatchUnexpectedState(batchId, current, expectedStates)

  • InvalidExchangeRate(batchId, totalDeposits, exchangeRate)

  • Unauthorized()

  • InvalidWrapperToken(token)

ReentrancyGuardTransient
  • ReentrancyGuardReentrantCall()

constructor(contract ERC7984ERC20Wrapper fromToken_, contract ERC7984ERC20Wrapper toToken_) internal

claim(uint256 batchId) → euint64 public

Claim the toToken corresponding to deposit in batch with id batchId.

quit(uint256 batchId) → euint64 public

Quit the batch with id batchId. Entire deposit is returned to the user. This can only be called if the batch has not yet been dispatched or if the batch was canceled.

Developers should consider adding additional restrictions to this function if maintaining confidentiality of deposits is critical to the application.

dispatchBatch() public

Permissionless function to dispatch the current batch. Increments the currentBatchId.

Developers should consider adding additional restrictions to this function if maintaining confidentiality of deposits is critical to the application.

dispatchBatchCallback(uint256 batchId, uint64 unwrapAmountCleartext, bytes decryptionProof) public

Dispatch batch callback callable by anyone. This function finalizes the unwrap of fromToken and calls _executeRoute to perform the batch’s route. If _executeRoute returns ExecuteOutcome.Partial, this function should be called again with the same batchId, unwrapAmountCleartext, and decryptionProof.

onConfidentialTransferReceived(address, address from, euint64 amount, bytes) → ebool external

Called upon receiving a confidential token transfer. Returns an encrypted boolean indicating success of the callback. If false is returned, the token contract will attempt to refund the transfer.

Do not manually refund the transfer AND return false, as this can lead to double refunds.

fromToken() → contract ERC7984ERC20Wrapper public

Batcher from token. Users deposit this token in exchange for toToken.

toToken() → contract ERC7984ERC20Wrapper public

Batcher to token. Users receive this token in exchange for their fromToken deposits.

currentBatchId() → uint256 public

The ongoing batch id. New deposits join this batch.

unwrapAmount(uint256 batchId) → euint64 public

The amount of fromToken unwrapped during dispatchBatch for batch with id batchId.

totalDeposits(uint256 batchId) → euint64 public

The total deposits made in batch with id batchId.

deposits(uint256 batchId, address account) → euint64 public

The deposits made by account in batch with id batchId.

exchangeRate(uint256 batchId) → uint64 public

The exchange rate set for batch with id batchId.

exchangeRateDecimals() → uint8 public

The number of decimals of precision for the exchange rate.

routeDescription() → string public

Human readable description of what the batcher does.

batchState(uint256 batchId) → enum BatcherConfidential.BatchState public

Returns the current state of a batch. Reverts if the batch does not exist.

_join(address to, euint64 amount) → euint64 internal

Joins a batch with amount amount on behalf of to. Does not do any transfers in. Returns the amount joined with.

_executeRoute(uint256 batchId, uint256 amount) → enum BatcherConfidential.ExecuteOutcome internal

Function which is executed by dispatchBatchCallback after validation and unwrap finalization. The parameter amount is the plaintext amount of the fromToken which were unwrapped—​to attain the underlying tokens received, evaluate amount * fromToken().rate(). This function should swap the underlying fromToken for underlying toToken.

This function returns an ExecuteOutcome enum indicating the new state of the batch. If the route execution is complete, the balance of the underlying toToken is wrapped and the exchange rate is set.

dispatchBatchCallback (and in turn _executeRoute) can be repeatedly called until the route execution is complete. If a multi-step route is necessary, intermediate steps should return ExecuteOutcome.Partial. Intermediate steps must not result in underlying toToken being transferred into the batcher.
This function must eventually return ExecuteOutcome.Complete or ExecuteOutcome.Cancel. Failure to do so results in user deposits being locked indefinitely.

_validateStateBitmap(uint256 batchId, bytes32 allowedStates) → enum BatcherConfidential.BatchState internal

Check that the current state of a batch matches the requirements described by the allowedStates bitmap. This bitmap should be built using _encodeStateBitmap.

If requirements are not met, reverts with a BatchUnexpectedState error.

_calculateUnwrapAmount(euint64 requestedUnwrapAmount) → euint64 internal

Mirror calculations done on the token to attain the same cipher-text for unwrap tracking.

_getAndIncreaseBatchId() → uint256 internal

Gets the current batch id and increments it.

_encodeStateBitmap(enum BatcherConfidential.BatchState batchState_) → bytes32 internal

Encodes a BatchState into a bytes32 representation where each bit enabled corresponds to the underlying position in the BatchState enum. For example:

0x000…​1000 ^--- Canceled ^-- Finalized ^- Dispatched ^ Pending

BatchDispatched(uint256 indexed batchId) event

Emitted when a batch with id batchId is dispatched via dispatchBatch.

BatchCanceled(uint256 indexed batchId) event

Emitted when a batch with id batchId is canceled.

BatchFinalized(uint256 indexed batchId, uint64 exchangeRate) event

Emitted when a batch with id batchId is finalized with an exchange rate of exchangeRate.

Joined(uint256 indexed batchId, address indexed account, euint64 amount) event

Emitted when an account joins a batch with id batchId with a deposit of amount.

Claimed(uint256 indexed batchId, address indexed account, euint64 amount) event

Emitted when an account claims their amount from batch with id batchId.

Quit(uint256 indexed batchId, address indexed account, euint64 amount) event

Emitted when an account quits a batch with id batchId.

BatchNonexistent(uint256 batchId) error

The batchId does not exist. Batch IDs start at 1 and must be less than or equal to currentBatchId.

BatchUnexpectedState(uint256 batchId, enum BatcherConfidential.BatchState current, bytes32 expectedStates) error

The batch batchId is in the state current, which is invalid for the operation. The expectedStates is a bitmap encoding the expected/allowed states for the operation.

InvalidExchangeRate(uint256 batchId, uint256 totalDeposits, uint64 exchangeRate) error

Thrown when the given exchange rate is invalid. The exchange rate must be non-zero and the wrapped amount of toToken must be less than or equal to type(uint64).max.

Unauthorized() error

The caller is not authorized to call this function.

InvalidWrapperToken(address token) error

The given token does not support IERC7984ERC20Wrapper via ERC165.