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
5 changes: 5 additions & 0 deletions .changeset/long-items-appear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-confidential-contracts': minor
---

`ERC7984ERC20Wrapper`: Support ERC-165 interface detection on `ERC7984ERC20Wrapper`.
30 changes: 30 additions & 0 deletions contracts/interfaces/IERC7984ERC20Wrapper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

import {externalEuint64, euint64} from "@fhevm/solidity/lib/FHE.sol";
import {IERC7984} from "./IERC7984.sol";

/// @dev Interface for ERC7984ERC20Wrapper contract.
interface IERC7984ERC20Wrapper is IERC7984 {
/// @dev Wraps `amount` of the underlying token into a confidential token and sends it to `to`.
function wrap(address to, uint256 amount) external;

/**
* @dev Unwraps tokens from `from` and sends the underlying tokens to `to`. The caller must be `from`
* or be an approved operator for `from`.
*
* Returns amount unwrapped.
*
* NOTE: The caller *must* already be approved by ACL for the given `amount`.
*/
function unwrap(
address from,
address to,
externalEuint64 encryptedAmount,
bytes calldata inputProof
) external returns (euint64);

/// @dev Returns the address of the underlying ERC-20 token that is being wrapped.
function underlying() external view returns (address);
}
5 changes: 4 additions & 1 deletion contracts/interfaces/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,7 @@ These interfaces are available as `.sol` files and are useful to interact with t
== Core
{{IERC7984}}
{{IERC7984Receiver}}
{{IERC7984Rwa}}

== Extensions
{{IERC7984Rwa}}
{{IERC7984ERC20Wrapper}}
54 changes: 28 additions & 26 deletions contracts/token/ERC7984/extensions/ERC7984ERC20Wrapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import {IERC1363Receiver} from "@openzeppelin/contracts/interfaces/IERC1363Recei
import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {IERC7984} from "../../../interfaces/IERC7984.sol";
import {IERC7984ERC20Wrapper} from "../../../interfaces/IERC7984ERC20Wrapper.sol";
import {ERC7984} from "./../ERC7984.sol";

/**
Expand All @@ -19,7 +22,7 @@ import {ERC7984} from "./../ERC7984.sol";
* WARNING: Minting assumes the full amount of the underlying token transfer has been received, hence some non-standard
* tokens such as fee-on-transfer or other deflationary-type tokens are not supported by this wrapper.
*/
abstract contract ERC7984ERC20Wrapper is ERC7984, IERC1363Receiver {
abstract contract ERC7984ERC20Wrapper is ERC7984, IERC7984ERC20Wrapper, IERC1363Receiver {
IERC20 private immutable _underlying;
uint8 private immutable _decimals;
uint256 private immutable _rate;
Expand Down Expand Up @@ -58,52 +61,43 @@ abstract contract ERC7984ERC20Wrapper is ERC7984, IERC1363Receiver {
bytes calldata data
) public virtual returns (bytes4) {
// check caller is the token contract
require(address(underlying()) == msg.sender, ERC7984UnauthorizedCaller(msg.sender));
require(underlying() == msg.sender, ERC7984UnauthorizedCaller(msg.sender));

// mint confidential token
address to = data.length < 20 ? from : address(bytes20(data));
_mint(to, FHE.asEuint64(SafeCast.toUint64(amount / rate())));

// transfer excess back to the sender
uint256 excess = amount % rate();
if (excess > 0) SafeERC20.safeTransfer(underlying(), from, excess);
if (excess > 0) SafeERC20.safeTransfer(IERC20(underlying()), from, excess);

// return magic value
return IERC1363Receiver.onTransferReceived.selector;
}

/**
* @dev Wraps amount `amount` of the underlying token into a confidential token and sends it to
* `to`. Tokens are exchanged at a fixed rate specified by {rate} such that `amount / rate()` confidential
* tokens are sent. Amount transferred in is rounded down to the nearest multiple of {rate}.
* @dev See {IERC7984ERC20Wrapper-wrap}. Tokens are exchanged at a fixed rate specified by {rate} such that
* `amount / rate()` confidential tokens are sent. The amount transferred in is rounded down to the nearest
* multiple of {rate}.
*/
function wrap(address to, uint256 amount) public virtual {
function wrap(address to, uint256 amount) public virtual override {
// take ownership of the tokens
SafeERC20.safeTransferFrom(underlying(), msg.sender, address(this), amount - (amount % rate()));
SafeERC20.safeTransferFrom(IERC20(underlying()), msg.sender, address(this), amount - (amount % rate()));

// mint confidential token
_mint(to, FHE.asEuint64(SafeCast.toUint64(amount / rate())));
}

/**
* @dev Unwraps tokens from `from` and sends the underlying tokens to `to`. The caller must be `from`
* or be an approved operator for `from`. `amount * rate()` underlying tokens are sent to `to`.
*
* Returns amount unwrapped.
*
* NOTE: The unwrap request created by this function must be finalized by calling {finalizeUnwrap}.
* NOTE: The caller *must* already be approved by ACL for the given `amount`.
*/
/// @dev Unwrap without passing an input proof. See {unwrap-address-address-bytes32-bytes} for more details.
function unwrap(address from, address to, euint64 amount) public virtual returns (euint64) {
require(FHE.isAllowed(amount, msg.sender), ERC7984UnauthorizedUseOfEncryptedAmount(amount, msg.sender));
return _unwrap(from, to, amount);
}

/**
* @dev Variant of {unwrap} that passes an `inputProof` which approves the caller for the `encryptedAmount`
* in the ACL.
* @dev See {IERC7984ERC20Wrapper-unwrap}. `amount * rate()` underlying tokens are sent to `to`.
*
* Returns amount unwrapped.
* NOTE: The unwrap request created by this function must be finalized by calling {finalizeUnwrap}.
*/
function unwrap(
address from,
Expand Down Expand Up @@ -131,13 +125,13 @@ abstract contract ERC7984ERC20Wrapper is ERC7984, IERC1363Receiver {

FHE.checkSignatures(handles, cleartexts, decryptionProof);

SafeERC20.safeTransfer(underlying(), to, unwrapAmountCleartext * rate());
SafeERC20.safeTransfer(IERC20(underlying()), to, unwrapAmountCleartext * rate());

emit UnwrapFinalized(to, unwrapAmount, unwrapAmountCleartext);
}

/// @inheritdoc ERC7984
function decimals() public view virtual override returns (uint8) {
function decimals() public view virtual override(IERC7984, ERC7984) returns (uint8) {
return _decimals;
}

Expand All @@ -149,9 +143,17 @@ abstract contract ERC7984ERC20Wrapper is ERC7984, IERC1363Receiver {
return _rate;
}

/// @dev Returns the address of the underlying ERC-20 token that is being wrapped.
function underlying() public view returns (IERC20) {
return _underlying;
/// @inheritdoc IERC7984ERC20Wrapper
function underlying() public view virtual override returns (address) {
return address(_underlying);
}

/// @inheritdoc IERC165
function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC7984) returns (bool) {
return
interfaceId == type(IERC7984ERC20Wrapper).interfaceId ||
interfaceId == type(IERC1363Receiver).interfaceId ||
super.supportsInterface(interfaceId);
}

/**
Expand All @@ -163,7 +165,7 @@ abstract contract ERC7984ERC20Wrapper is ERC7984, IERC1363Receiver {
* on {finalizeUnwrap}.
*/
function inferredTotalSupply() public view virtual returns (uint256) {
return underlying().balanceOf(address(this)) / rate();
return IERC20(underlying()).balanceOf(address(this)) / rate();
}

/// @dev Returns the maximum total supply of wrapped tokens supported by the encrypted datatype.
Expand Down
Loading