Skip to content
Closed
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
8 changes: 8 additions & 0 deletions contracts/interfaces/IERC7984.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ interface IERC7984 {
/// @dev Emitted when a confidential transfer is made from `from` to `to` of encrypted amount `amount`.
event ConfidentialTransfer(address indexed from, address indexed to, euint64 indexed amount);

/**
* @dev Emitted when an encrypted amount is requested for disclosure by `requester`.
*
* Both `requester` and contract must have permission to access the encrypted amount `encryptedAmount`
* to request disclosure of the encrypted amount `encryptedAmount`
*/
event AmountDiscloseRequested(euint64 indexed encryptedAmount, address indexed requester);

/**
* @dev Emitted when an encrypted amount is disclosed.
*
Expand Down
48 changes: 24 additions & 24 deletions contracts/mocks/docs/SwapERC7984ToERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,28 @@ contract SwapConfidentialToERC20 {
_toToken = toToken;
}

function swapConfidentialToERC20(externalEuint64 encryptedInput, bytes memory inputProof) public {
euint64 amount = FHE.fromExternal(encryptedInput, inputProof);
FHE.allowTransient(amount, address(_fromToken));
euint64 amountTransferred = _fromToken.confidentialTransferFrom(msg.sender, address(this), amount);

bytes32[] memory cts = new bytes32[](1);
cts[0] = euint64.unwrap(amountTransferred);
uint256 requestID = FHE.requestDecryption(cts, this.finalizeSwap.selector);

// register who is getting the tokens
_receivers[requestID] = msg.sender;
}

function finalizeSwap(uint256 requestID, bytes calldata cleartexts, bytes calldata decryptionProof) public virtual {
FHE.checkSignatures(requestID, cleartexts, decryptionProof);
uint64 amount = abi.decode(cleartexts, (uint64));
address to = _receivers[requestID];
require(to != address(0), SwapConfidentialToERC20InvalidGatewayRequest(requestID));
delete _receivers[requestID];

if (amount != 0) {
SafeERC20.safeTransfer(_toToken, to, amount);
}
}
// function swapConfidentialToERC20(externalEuint64 encryptedInput, bytes memory inputProof) public {
// euint64 amount = FHE.fromExternal(encryptedInput, inputProof);
// FHE.allowTransient(amount, address(_fromToken));
// euint64 amountTransferred = _fromToken.confidentialTransferFrom(msg.sender, address(this), amount);

// bytes32[] memory cts = new bytes32[](1);
// cts[0] = euint64.unwrap(amountTransferred);
// uint256 requestID = FHE.requestDecryption(cts, this.finalizeSwap.selector);

// // register who is getting the tokens
// _receivers[requestID] = msg.sender;
// }

// function finalizeSwap(uint256 requestID, bytes calldata cleartexts, bytes calldata decryptionProof) public virtual {
// FHE.checkSignatures(requestID, cleartexts, decryptionProof);
// uint64 amount = abi.decode(cleartexts, (uint64));
// address to = _receivers[requestID];
// require(to != address(0), SwapConfidentialToERC20InvalidGatewayRequest(requestID));
// delete _receivers[requestID];

// if (amount != 0) {
// SafeERC20.safeTransfer(_toToken, to, amount);
// }
// }
Comment on lines +21 to +44
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Non-functional contract: track or implement the missing swap logic.

The entire swap workflow is commented out, leaving the contract with no executable functionality beyond construction. While the PR objectives acknowledge that the logic was unclear, this creates technical debt that should be explicitly tracked.

Concerns:

  • State variables and constructor are initialized but never used
  • Deploying this contract would be non-functional and waste gas
  • No documentation (TODO/FIXME) indicating the migration path or expected timeline
  • The commented code shows a mix of old (requestDecryption at line 28) and new (checkSignatures at line 35) API patterns, suggesting the migration requirements are not yet fully understood

Recommendations:

  1. Add a TODO/FIXME comment at the contract level explaining the missing implementation
  2. Open a tracking issue for porting this functionality to FHEVM v9
  3. Consider removing this contract entirely if it's not needed in this PR, or mark it clearly as a non-functional placeholder
  4. Document the specific clarifications needed about the "expected logic" mentioned in the PR description

Would you like me to help implement the FHEVM v9 migration for these functions based on patterns from other contracts in the PR, or open a tracking issue for this work?

🤖 Prompt for AI Agents
In contracts/mocks/docs/SwapERC7984ToERC20.sol around lines 21-44, the swap
workflow is fully commented out leaving the contract non-functional; add a
top-of-contract TODO/FIXME noting it's a non-functional placeholder and that
swap logic must be migrated to FHEVM v9, open a tracking issue linking this file
and describing the missing functions (swapConfidentialToERC20 and finalizeSwap)
and the API mismatches (requestDecryption vs checkSignatures), and either remove
the contract from the PR if unused or add a clear NOP marker and deploy-blocker
comment indicating it must not be used in production; also add a short checklist
of the clarifications needed (expected cleartext format, receiver handling, and
exact FHEVM v9 API calls) so whoever picks up the issue can implement the
migration.

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {SepoliaConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
import {EthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
import {VestingWalletCliffConfidential} from "../../finance/VestingWalletCliffConfidential.sol";

abstract contract VestingWalletCliffConfidentialMock is VestingWalletCliffConfidential, SepoliaConfig {}
abstract contract VestingWalletCliffConfidentialMock is VestingWalletCliffConfidential, EthereumConfig {}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {SepoliaConfig, ZamaConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
import {EthereumConfig, ZamaConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
import {FHE} from "@fhevm/solidity/lib/FHE.sol";
import {ERC7821WithExecutor} from "./../../finance/ERC7821WithExecutor.sol";
import {VestingWalletCliffConfidential} from "./../../finance/VestingWalletCliffConfidential.sol";
import {VestingWalletConfidentialFactory} from "./../../finance/VestingWalletConfidentialFactory.sol";

abstract contract VestingWalletConfidentialFactoryMock is VestingWalletConfidentialFactory, SepoliaConfig {
abstract contract VestingWalletConfidentialFactoryMock is VestingWalletConfidentialFactory, EthereumConfig {
function _deployVestingWalletImplementation() internal virtual override returns (address) {
return address(new VestingWalletCliffExecutorConfidential());
}
Expand Down Expand Up @@ -46,7 +46,7 @@ abstract contract VestingWalletConfidentialFactoryMock is VestingWalletConfident
}

// slither-disable-next-line locked-ether
contract VestingWalletCliffExecutorConfidential is VestingWalletCliffConfidential, ERC7821WithExecutor, SepoliaConfig {
contract VestingWalletCliffExecutorConfidential is VestingWalletCliffConfidential, ERC7821WithExecutor, EthereumConfig {
constructor() {
_disableInitializers();
}
Expand Down
4 changes: 2 additions & 2 deletions contracts/mocks/finance/VestingWalletConfidentialMock.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {SepoliaConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
import {EthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
import {VestingWalletConfidential} from "../../finance/VestingWalletConfidential.sol";

abstract contract VestingWalletConfidentialMock is VestingWalletConfidential, SepoliaConfig {}
abstract contract VestingWalletConfidentialMock is VestingWalletConfidential, EthereumConfig {}
4 changes: 2 additions & 2 deletions contracts/mocks/token/ERC7984ERC20WrapperMock.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

import {SepoliaConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
import {EthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
import {ERC7984ERC20Wrapper, ERC7984} from "../../token/ERC7984/extensions/ERC7984ERC20Wrapper.sol";

contract ERC7984ERC20WrapperMock is ERC7984ERC20Wrapper, SepoliaConfig {
contract ERC7984ERC20WrapperMock is ERC7984ERC20Wrapper, EthereumConfig {
constructor(
IERC20 token,
string memory name,
Expand Down
2 changes: 1 addition & 1 deletion contracts/mocks/token/ERC7984FreezableMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

pragma solidity ^0.8.27;

import {SepoliaConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
import {EthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
import {FHE, euint64, externalEuint64} from "@fhevm/solidity/lib/FHE.sol";
import {ERC7984} from "../../token/ERC7984/ERC7984.sol";
import {ERC7984Freezable} from "../../token/ERC7984/extensions/ERC7984Freezable.sol";
Expand Down
4 changes: 2 additions & 2 deletions contracts/mocks/token/ERC7984Mock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

pragma solidity ^0.8.27;

import {SepoliaConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
import {EthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
import {FHE, eaddress, euint64, externalEuint64} from "@fhevm/solidity/lib/FHE.sol";
import {ERC7984} from "../../token/ERC7984/ERC7984.sol";

// solhint-disable func-name-mixedcase
contract ERC7984Mock is ERC7984, SepoliaConfig {
contract ERC7984Mock is ERC7984, EthereumConfig {
address private immutable _OWNER;

event EncryptedAmountCreated(euint64 amount);
Expand Down
2 changes: 1 addition & 1 deletion contracts/mocks/token/ERC7984ObserverAccessMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

pragma solidity ^0.8.27;

import {SepoliaConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
import {EthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
import {FHE, euint64, externalEuint64} from "@fhevm/solidity/lib/FHE.sol";
import {ERC7984ObserverAccess} from "../../token/ERC7984/extensions/ERC7984ObserverAccess.sol";
import {ERC7984Mock} from "./ERC7984Mock.sol";
Expand Down
4 changes: 2 additions & 2 deletions contracts/mocks/token/ERC7984ReceiverMock.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

import {SepoliaConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
import {EthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
import {FHE, ebool, euint64} from "@fhevm/solidity/lib/FHE.sol";
import {IERC7984Receiver} from "../../interfaces/IERC7984Receiver.sol";

contract ERC7984ReceiverMock is IERC7984Receiver, SepoliaConfig {
contract ERC7984ReceiverMock is IERC7984Receiver, EthereumConfig {
event ConfidentialTransferCallback(bool success);

error InvalidInput(uint8 input);
Expand Down
4 changes: 2 additions & 2 deletions contracts/mocks/token/ERC7984RestrictedMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

pragma solidity ^0.8.27;

import {SepoliaConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
import {EthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
import {FHE} from "@fhevm/solidity/lib/FHE.sol";
import {ERC7984Restricted} from "../../token/ERC7984/extensions/ERC7984Restricted.sol";

abstract contract ERC7984RestrictedMock is ERC7984Restricted, SepoliaConfig {
abstract contract ERC7984RestrictedMock is ERC7984Restricted, EthereumConfig {
function _mint(address to, uint64 amount) internal {
_mint(to, FHE.asEuint64(amount));
}
Expand Down
4 changes: 2 additions & 2 deletions contracts/mocks/utils/FHESafeMathMock.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

import {SepoliaConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
import {EthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
import {FHE, ebool, euint64, externalEuint64} from "@fhevm/solidity/lib/FHE.sol";
import {FHESafeMath} from "./../../utils/FHESafeMath.sol";

contract FHESafeMathMock is SepoliaConfig {
contract FHESafeMathMock is EthereumConfig {
Comment on lines +4 to +8
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

EthereumConfig still wires zeroed coprocessor/oracle addresses

EthereumConfig in the current ZamaConfig release is only a placeholder: getEthereumConfig() and getEthereumOracleAddress() both return address(0), and the docs explicitly state Sepolia is the only supported network config today. Inheriting EthereumConfig therefore sets the FHE coprocessor and oracle to zero, so every downstream FHE.* call (e.g., the FHESafeMath ops exercised here) will revert at runtime. Please keep inheriting SepoliaConfig (or provide non-zero mainnet addresses) before enabling these mocks. Same fix will be needed for the other mocks switched to EthereumConfig in this PR. (docs.zama.ai)

-import {EthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
+import {SepoliaConfig} from "@fhevm/solidity/config/ZamaConfig.sol";-contract FHESafeMathMock is EthereumConfig {
+contract FHESafeMathMock is SepoliaConfig {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import {EthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
import {FHE, ebool, euint64, externalEuint64} from "@fhevm/solidity/lib/FHE.sol";
import {FHESafeMath} from "./../../utils/FHESafeMath.sol";
contract FHESafeMathMock is SepoliaConfig {
contract FHESafeMathMock is EthereumConfig {
import {SepoliaConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
import {FHE, ebool, euint64, externalEuint64} from "@fhevm/solidity/lib/FHE.sol";
import {FHESafeMath} from "./../../utils/FHESafeMath.sol";
contract FHESafeMathMock is SepoliaConfig {
🤖 Prompt for AI Agents
In contracts/mocks/utils/FHESafeMathMock.sol around lines 4 to 8, the contract
inherits EthereumConfig which currently returns zeroed coprocessor/oracle
addresses causing all FHE.* calls to revert; change the base contract to
SepoliaConfig (or otherwise initialize non-zero mainnet coprocessor and oracle
addresses) so FHE operations have valid addresses, and apply the same change to
any other mock files in this PR that were switched to EthereumConfig.

event HandleCreated(euint64 amount);
event ResultComputed(ebool success, euint64 updated);

Expand Down
4 changes: 2 additions & 2 deletions contracts/mocks/utils/HandleAccessManagerMock.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {SepoliaConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
import {EthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
import {FHE, euint64} from "@fhevm/solidity/lib/FHE.sol";
import {HandleAccessManager} from "./../../utils/HandleAccessManager.sol";

contract HandleAccessManagerMock is HandleAccessManager, SepoliaConfig {
contract HandleAccessManagerMock is HandleAccessManager, EthereumConfig {
event HandleCreated(euint64 handle);

function _validateHandleAllowance(bytes32 handle) internal view override {}
Expand Down
4 changes: 2 additions & 2 deletions contracts/mocks/utils/HandleAccessManagerUserMock.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {SepoliaConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
import {EthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
import {Impl} from "@fhevm/solidity/lib/FHE.sol";
import {HandleAccessManager} from "./../../utils/HandleAccessManager.sol";

contract HandleAccessManagerUserMock is SepoliaConfig {
contract HandleAccessManagerUserMock is EthereumConfig {
function getTransientAllowance(HandleAccessManager allowance, bytes32 handle) public {
allowance.getHandleAllowance(handle, address(this), false);
require(Impl.isAllowed(handle, address(this)), "HandleAccessManagerUserMock: Not allowed");
Expand Down
15 changes: 8 additions & 7 deletions contracts/token/ERC7984/ERC7984.sol
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,8 @@ abstract contract ERC7984 is IERC7984 {
ERC7984UnauthorizedUseOfEncryptedAmount(encryptedAmount, msg.sender)
);

bytes32[] memory cts = new bytes32[](1);
cts[0] = euint64.unwrap(encryptedAmount);
FHE.requestDecryption(cts, this.finalizeDiscloseEncryptedAmount.selector);
FHE.makePubliclyDecryptable(encryptedAmount);
emit AmountDiscloseRequested(encryptedAmount, msg.sender);
}

/**
Expand All @@ -218,13 +217,15 @@ abstract contract ERC7984 is IERC7984 {
* unexpected `AmountDisclosed` events.
*/
function finalizeDiscloseEncryptedAmount(
uint256 requestId,
euint64 encryptedAmount,
bytes calldata cleartexts,
bytes calldata decryptionProof
) public virtual {
FHE.checkSignatures(requestId, cleartexts, decryptionProof);
euint64 requestHandle = euint64.wrap(FHE.loadRequestedHandles(requestId)[0]);
emit AmountDisclosed(requestHandle, abi.decode(cleartexts, (uint64)));
//ERC7984UnauthorizedUseOfEncryptedAmount(encryptedAmount, msg.sender)
bytes32[] memory cts = new bytes32[](1);
cts[0] = euint64.unwrap(encryptedAmount);
FHE.verifySignatures(cts, cleartexts, decryptionProof);
emit AmountDisclosed(encryptedAmount, abi.decode(cleartexts, (uint64)));
}

function _setOperator(address holder, address operator, uint48 until) internal virtual {
Expand Down
60 changes: 50 additions & 10 deletions contracts/token/ERC7984/extensions/ERC7984ERC20Wrapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,15 @@ abstract contract ERC7984ERC20Wrapper is ERC7984, IERC1363Receiver {
uint8 private immutable _decimals;
uint256 private immutable _rate;

/// @dev Mapping from gateway decryption request ID to the address that will receive the tokens
mapping(uint256 decryptionRequest => address) private _receivers;
uint256 private _counterUnwrapRequestId;
mapping(uint256 unwrapRequestId => UnwrapRequest unwrapRequest) private _unwrapRequests;

struct UnwrapRequest {
address receiver;
euint64 amount;
}

event UnwrapRequested(uint256 indexed requestID, address receiver, euint64 amount);

constructor(IERC20 underlying_) {
_underlying = underlying_;
Expand Down Expand Up @@ -124,6 +131,27 @@ abstract contract ERC7984ERC20Wrapper is ERC7984, IERC1363Receiver {
_unwrap(from, to, FHE.fromExternal(encryptedAmount, inputProof));
}

/**
* @notice Returns the total number of unwrap requests that have been initiated so far.
* @dev This count can be used by dApps to keep track of the overall number of requests
* and to check the status of a specific request ID.
* @return The current total number of unwrap requests.
*/
function getUnwrapRequestCount() public view returns (uint256) {
return _counterUnwrapRequestId;
}

/**
* @notice Checks the status of a specific unwrap request ID.
* @dev An unwrap request is considered finalized if its ID is within the valid range (i.e., less than the total count)
* AND its `UnwrapRequest` struct has been deleted after processing.
* @param requestID The ID of the unwrap request to check (requestID > 0).
* @return true if the unwrap request has been successfully finalized; false otherwise.
*/
function unwrapFinalized(uint256 requestID) public view returns (bool) {
return (requestID <= _counterUnwrapRequestId && _unwrapRequests[requestID].receiver == address(0));
}

/**
* @dev Fills an unwrap request for a given request id related to a decrypted unwrap amount.
*/
Expand All @@ -132,10 +160,18 @@ abstract contract ERC7984ERC20Wrapper is ERC7984, IERC1363Receiver {
bytes calldata cleartexts,
bytes calldata decryptionProof
) public virtual {
FHE.checkSignatures(requestID, cleartexts, decryptionProof);
address to = _receivers[requestID];
address to = _unwrapRequests[requestID].receiver;
require(to != address(0), ERC7984InvalidGatewayRequest(requestID));
delete _receivers[requestID];

bytes32 handle = FHE.toBytes32(_unwrapRequests[requestID].amount);
require(handle != bytes32(0), ERC7984InvalidGatewayRequest(requestID));

delete _unwrapRequests[requestID];
// From now on: unwrapFinalized(requestID) == true

bytes32[] memory handlesList = new bytes32[](1);
handlesList[0] = handle;
FHE.verifySignatures(handlesList, cleartexts, decryptionProof);

SafeERC20.safeTransfer(underlying(), to, abi.decode(cleartexts, (uint64)) * rate());
}
Expand All @@ -144,16 +180,20 @@ abstract contract ERC7984ERC20Wrapper is ERC7984, IERC1363Receiver {
require(to != address(0), ERC7984InvalidReceiver(to));
require(from == msg.sender || isOperator(from, msg.sender), ERC7984UnauthorizedSpender(from, msg.sender));

_counterUnwrapRequestId++;

// Starts at 1, requestID > 0
uint256 requestID = _counterUnwrapRequestId;

// try to burn, see how much we actually got
euint64 burntAmount = _burn(from, amount);

// decrypt that burntAmount
bytes32[] memory cts = new bytes32[](1);
cts[0] = euint64.unwrap(burntAmount);
uint256 requestID = FHE.requestDecryption(cts, this.finalizeUnwrap.selector);
FHE.makePubliclyDecryptable(burntAmount);

// register who is getting the tokens
_receivers[requestID] = to;
_unwrapRequests[requestID] = UnwrapRequest({receiver: to, amount: burntAmount});

emit UnwrapRequested(requestID, to, burntAmount);
}

/**
Expand Down
Loading