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
93 changes: 0 additions & 93 deletions packages/hardhat/contracts/ConfidentialClaim.sol

This file was deleted.

136 changes: 34 additions & 102 deletions packages/hardhat/contracts/ConfidentialERC20.sol
Original file line number Diff line number Diff line change
@@ -1,118 +1,50 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/ERC20.sol)

pragma solidity ^0.8.25;

import { IERC20, IERC20Metadata, ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { IFHERC20, FHERC20 } from "./FHERC20.sol";
import { euint128, FHE } from "@fhenixprotocol/cofhe-contracts/FHE.sol";
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import { ConfidentialClaim } from "./ConfidentialClaim.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract ConfidentialERC20 is FHERC20, Ownable, ConfidentialClaim {
using EnumerableSet for EnumerableSet.UintSet;
using SafeERC20 for IERC20;

IERC20 private immutable _erc20;
string private _symbol;

event EncryptedERC20(address indexed from, address indexed to, uint128 value);
event DecryptedERC20(address indexed from, address indexed to, uint128 value);
event ClaimedDecryptedERC20(address indexed from, address indexed to, uint128 value);
event SymbolUpdated(string symbol);

/**
* @dev The erc20 token couldn't be wrapped.
*/
error FHERC20InvalidErc20(address token);

/**
* @dev The recipient is the zero address.
*/
error InvalidRecipient();
import { ERC7984 } from "fhenix-confidential-contracts/contracts/ERC7984/ERC7984.sol";
import { ERC7984ERC20Wrapper } from "fhenix-confidential-contracts/contracts/ERC7984/extensions/ERC7984ERC20Wrapper.sol";
import { IERC7984 } from "fhenix-confidential-contracts/contracts/interfaces/IERC7984.sol";

/**
* @dev Confidential ERC-20 wrapper deployed by {RedactCore}.
*
* Wraps a standard ERC-20 token into a confidential {ERC7984} token.
* Name is auto-generated as `"ERC7984 Confidential <underlyingName>"` and
* symbol as `"e<underlyingSymbol>"`.
*/
contract ConfidentialERC20 is ERC7984ERC20Wrapper, Ownable {
error InvalidUnderlying(address token);

constructor(
IERC20 erc20_,
string memory symbolOverride_
IERC20 erc20_
)
Ownable(msg.sender)
FHERC20(
string.concat("Confidential ", IERC20Metadata(address(erc20_)).name()),
bytes(symbolOverride_).length == 0
? string.concat("e", IERC20Metadata(address(erc20_)).symbol())
: symbolOverride_,
IERC20Metadata(address(erc20_)).decimals()
ERC7984(
string.concat("ERC7984 Confidential ", IERC20Metadata(address(erc20_)).name()),
string.concat("e", IERC20Metadata(address(erc20_)).symbol()),
_cappedDecimals(address(erc20_)),
""
)
ERC7984ERC20Wrapper(erc20_)
Ownable(msg.sender)
{
try IFHERC20(address(erc20_)).isFherc20() returns (bool isFherc20) {
if (isFherc20) {
revert FHERC20InvalidErc20(address(erc20_));
}
} catch {
// Not an FHERC20, continue
}

_erc20 = erc20_;

_symbol = bytes(symbolOverride_).length == 0
? string.concat("e", IERC20Metadata(address(erc20_)).symbol())
: symbolOverride_;
if (_isERC7984(address(erc20_))) revert InvalidUnderlying(address(erc20_));
}

function symbol() public view override returns (string memory) {
return _symbol;
function _cappedDecimals(address token) private view returns (uint8) {
(bool ok, bytes memory data) = token.staticcall(abi.encodeCall(IERC20Metadata.decimals, ()));
uint8 d = (ok && data.length == 32) ? abi.decode(data, (uint8)) : 18;
return d > 6 ? 6 : d;
}

function updateSymbol(string memory updatedSymbol) public onlyOwner {
_symbol = updatedSymbol;
emit SymbolUpdated(updatedSymbol);
}

/**
* @dev Returns the address of the erc20 ERC-20 token that is being encrypted wrapped.
*/
function erc20() public view returns (IERC20) {
return _erc20;
}

function encrypt(address to, uint128 value) public {
if (to == address(0)) to = msg.sender;
_erc20.safeTransferFrom(msg.sender, address(this), value);
_mint(to, value);
emit EncryptedERC20(msg.sender, to, value);
}

function decrypt(address to, uint128 value) public {
if (to == address(0)) to = msg.sender;
euint128 burned = _burn(msg.sender, value);
FHE.decrypt(burned);
_createClaim(to, value, burned);
emit DecryptedERC20(msg.sender, to, value);
}

/**
* @notice Claim a decrypted amount of the underlying ERC20
* @param ctHash The ctHash of the burned amount
*/
function claimDecrypted(uint256 ctHash) public {
Claim memory claim = _handleClaim(ctHash);

// Send the ERC20 to the recipient
_erc20.safeTransfer(claim.to, claim.decryptedAmount);
emit ClaimedDecryptedERC20(msg.sender, claim.to, claim.decryptedAmount);
}

/**
* @notice Claim all decrypted amounts of the underlying ERC20
*/
function claimAllDecrypted() public {
Claim[] memory claims = _handleClaimAll();

for (uint256 i = 0; i < claims.length; i++) {
_erc20.safeTransfer(claims[i].to, claims[i].decryptedAmount);
emit ClaimedDecryptedERC20(msg.sender, claims[i].to, claims[i].decryptedAmount);
function _isERC7984(address token) private view returns (bool) {
try IERC165(token).supportsInterface(type(IERC7984).interfaceId) returns (bool supported) {
return supported;
} catch {
return false;
}
}
}
119 changes: 24 additions & 95 deletions packages/hardhat/contracts/ConfidentialETH.sol
Original file line number Diff line number Diff line change
@@ -1,102 +1,31 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.25;

import { IERC20, IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { FHERC20 } from "./FHERC20.sol";
import { IWETH } from "./interfaces/IWETH.sol";
import { euint128, FHE } from "@fhenixprotocol/cofhe-contracts/FHE.sol";
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import { ConfidentialClaim } from "./ConfidentialClaim.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract ConfidentialETH is FHERC20, Ownable, ConfidentialClaim {
using EnumerableSet for EnumerableSet.UintSet;
using SafeERC20 for IERC20;
using SafeERC20 for IWETH;

IWETH public wETH;

import { ERC7984 } from "fhenix-confidential-contracts/contracts/ERC7984/ERC7984.sol";
import {
ERC7984NativeWrapper,
IWETH
} from "fhenix-confidential-contracts/contracts/ERC7984/extensions/ERC7984NativeWrapper.sol";

/**
* @dev Confidential native-token wrapper deployed externally and registered with {RedactCore}.
*
* Wraps native ETH (or any WETH-compatible asset) into a confidential {ERC7984} token.
* Name is fixed as `"ERC7984 Confidential Ether"` and symbol as `"eETH"`.
*/
contract ConfidentialETH is ERC7984NativeWrapper, Ownable {
constructor(
IWETH wETH_
) Ownable(msg.sender) FHERC20("Confidential Wrapped ETHER", "eETH", IERC20Metadata(address(wETH_)).decimals()) {
wETH = wETH_;
}

/**
* @dev Returns the address of the erc20 ERC-20 token that is being encrypted wrapped.
*/
function erc20() public view returns (IERC20) {
return wETH;
}

receive() external payable {}

fallback() external payable {}

event EncryptedWETH(address indexed from, address indexed to, uint128 value);
event EncryptedETH(address indexed from, address indexed to, uint256 value);
event DecryptedETH(address indexed from, address indexed to, uint128 value);
event ClaimedDecryptedETH(address indexed from, address indexed to, uint128 value);

/**
* @dev The ETH transfer failed.
*/
error ETHTransferFailed();

/**
* @dev The recipient is the zero address.
*/
error InvalidRecipient();

function encryptWETH(address to, uint128 value) public {
if (to == address(0)) to = msg.sender;
wETH.safeTransferFrom(msg.sender, address(this), value);
wETH.withdraw(value);
_mint(to, value);
emit EncryptedWETH(msg.sender, to, value);
}

function encryptETH(address to) public payable {
if (to == address(0)) to = msg.sender;
_mint(to, SafeCast.toUint128(msg.value));
emit EncryptedETH(msg.sender, to, msg.value);
}

function decrypt(address to, uint128 value) public {
if (to == address(0)) to = msg.sender;
euint128 burned = _burn(msg.sender, value);
FHE.decrypt(burned);
_createClaim(to, value, burned);
emit DecryptedETH(msg.sender, to, value);
}

/**
* @notice Claim a decrypted amount of ETH
* @param ctHash The ctHash of the burned amount
*/
function claimDecrypted(uint256 ctHash) public {
Claim memory claim = _handleClaim(ctHash);

// Send the ETH to the recipient
(bool sent, ) = claim.to.call{ value: claim.decryptedAmount }("");
if (!sent) revert ETHTransferFailed();

emit ClaimedDecryptedETH(msg.sender, claim.to, claim.decryptedAmount);
}

/**
* @notice Claim all decrypted amounts of ETH
*/
function claimAllDecrypted() public {
Claim[] memory claims = _handleClaimAll();

for (uint256 i = 0; i < claims.length; i++) {
(bool sent, ) = claims[i].to.call{ value: claims[i].decryptedAmount }("");
if (!sent) revert ETHTransferFailed();
emit ClaimedDecryptedETH(msg.sender, claims[i].to, claims[i].decryptedAmount);
}
IWETH weth_
)
ERC7984("ERC7984 Confidential Ether", "eETH", _cappedDecimals(address(weth_)), "")
ERC7984NativeWrapper(weth_)
Ownable(msg.sender)
{}

function _cappedDecimals(address token) private view returns (uint8) {
uint8 d = IERC20Metadata(token).decimals();
return d > 6 ? 6 : d;
}
}
Loading