-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #64 from PacificYield/erc20-wrapper
feat: ConfidentialERC20Wrapped/ConfidentialWETH
- Loading branch information
Showing
13 changed files
with
1,062 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
10 changes: 10 additions & 0 deletions
10
contracts/test/token/ERC20/TestConfidentialERC20Wrapped.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
// SPDX-License-Identifier: BSD-3-Clause-Clear | ||
pragma solidity ^0.8.24; | ||
|
||
import { ConfidentialERC20Wrapped } from "../../../token/ERC20/ConfidentialERC20Wrapped.sol"; | ||
import { SepoliaZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; | ||
import { SepoliaZamaGatewayConfig } from "fhevm/config/ZamaGatewayConfig.sol"; | ||
|
||
contract TestConfidentialERC20Wrapped is SepoliaZamaFHEVMConfig, SepoliaZamaGatewayConfig, ConfidentialERC20Wrapped { | ||
constructor(address erc20_, uint256 maxDecryptionDelay_) ConfidentialERC20Wrapped(erc20_, maxDecryptionDelay_) {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
// SPDX-License-Identifier: BSD-3-Clause-Clear | ||
pragma solidity ^0.8.24; | ||
|
||
import { ConfidentialWETH } from "../../../token/ERC20/ConfidentialWETH.sol"; | ||
import { SepoliaZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; | ||
import { SepoliaZamaGatewayConfig } from "fhevm/config/ZamaGatewayConfig.sol"; | ||
|
||
contract TestConfidentialWETH is SepoliaZamaFHEVMConfig, SepoliaZamaGatewayConfig, ConfidentialWETH { | ||
constructor(uint256 maxDecryptionDelay_) ConfidentialWETH(maxDecryptionDelay_) {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
// SPDX-License-Identifier: BSD-3-Clause-Clear | ||
pragma solidity ^0.8.24; | ||
|
||
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | ||
import { Ownable2Step, Ownable } from "@openzeppelin/contracts/access/Ownable2Step.sol"; | ||
|
||
/** | ||
* @title ERC20Mintable | ||
* @notice This contract is an ERC20 token that is mintable by the owner. | ||
*/ | ||
contract ERC20Mintable is ERC20, Ownable2Step { | ||
/// @dev override number of decimals | ||
uint8 private immutable _DECIMALS; | ||
|
||
constructor( | ||
string memory name_, | ||
string memory symbol_, | ||
uint8 decimals_, | ||
address owner_ | ||
) ERC20(name_, symbol_) Ownable(owner_) { | ||
_DECIMALS = decimals_; | ||
} | ||
|
||
/** | ||
* @notice Returns the number of decimals. | ||
*/ | ||
function decimals() public view override returns (uint8) { | ||
return _DECIMALS; | ||
} | ||
|
||
/** | ||
* @notice Mint tokens. | ||
* @param amount Amount of tokens to mint. | ||
*/ | ||
function mint(uint256 amount) public onlyOwner { | ||
_mint(msg.sender, amount); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
// SPDX-License-Identifier: BSD-3-Clause-Clear | ||
pragma solidity ^0.8.24; | ||
|
||
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; | ||
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; | ||
import { ReentrancyGuardTransient } from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol"; | ||
|
||
import "fhevm/lib/TFHE.sol"; | ||
import "fhevm/gateway/GatewayCaller.sol"; | ||
|
||
import { IConfidentialERC20Wrapped } from "./IConfidentialERC20Wrapped.sol"; | ||
import { ConfidentialERC20 } from "./ConfidentialERC20.sol"; | ||
|
||
/** | ||
* @title ConfidentialERC20Wrapped | ||
* @notice This contract allows users to wrap/unwrap trustlessly | ||
* ERC20 tokens to ConfidentialERC20 tokens. | ||
* @dev This implementation does not support tokens with rebase functions or | ||
* tokens with a fee on transfer. All ERC20 tokens must have decimals | ||
* inferior or equal to 18 decimals but superior or equal to 6 decimals. | ||
*/ | ||
abstract contract ConfidentialERC20Wrapped is | ||
ConfidentialERC20, | ||
IConfidentialERC20Wrapped, | ||
ReentrancyGuardTransient, | ||
GatewayCaller | ||
{ | ||
using SafeERC20 for IERC20Metadata; | ||
|
||
/// @notice Returned if the maximum decryption delay is higher than 1 day. | ||
error MaxDecryptionDelayTooHigh(); | ||
|
||
/// @notice ERC20 token that is wrapped. | ||
IERC20Metadata public immutable ERC20_TOKEN; | ||
|
||
/// @notice Tracks whether the account can move funds. | ||
mapping(address account => bool isRestricted) public isAccountRestricted; | ||
|
||
/// @notice Tracks the unwrap request to a unique request id. | ||
mapping(uint256 requestId => UnwrapRequest unwrapRequest) public unwrapRequests; | ||
|
||
/** | ||
* @notice Deposit/withdraw ERC20 tokens using confidential ERC20 tokens. | ||
* @param erc20_ Address of the ERC20 token to wrap/unwrap. | ||
* @dev The name/symbol are autogenerated. | ||
* For instance, | ||
* "Wrapped Ether" --> "Confidential Wrapped Ether" | ||
* "WETH" --> "WETHc". | ||
* @param maxDecryptionDelay_ Maximum delay for the Gateway to decrypt. | ||
* @dev Do not use a small value in production to avoid security issues if the response | ||
* cannot be processed because the block time is higher than the delay. | ||
* The current implementation expects the Gateway to always return a decrypted | ||
* value within the delay specified, as long as it is sufficient enough. | ||
*/ | ||
constructor( | ||
address erc20_, | ||
uint256 maxDecryptionDelay_ | ||
) | ||
ConfidentialERC20( | ||
string(abi.encodePacked("Confidential ", IERC20Metadata(erc20_).name())), | ||
string(abi.encodePacked(IERC20Metadata(erc20_).symbol(), "c")) | ||
) | ||
{ | ||
ERC20_TOKEN = IERC20Metadata(erc20_); | ||
|
||
/// @dev The maximum delay is set to 1 day. | ||
if (maxDecryptionDelay_ > 1 days) { | ||
revert MaxDecryptionDelayTooHigh(); | ||
} | ||
} | ||
|
||
/** | ||
* @notice Unwrap ConfidentialERC20 tokens to standard ERC20 tokens. | ||
* @param amount Amount to unwrap. | ||
*/ | ||
function unwrap(uint64 amount) public virtual { | ||
_canTransferOrUnwrap(msg.sender); | ||
|
||
/// @dev Once this function is called, it becomes impossible for the sender to move any token. | ||
isAccountRestricted[msg.sender] = true; | ||
ebool canUnwrap = TFHE.le(amount, _balances[msg.sender]); | ||
|
||
uint256[] memory cts = new uint256[](1); | ||
cts[0] = Gateway.toUint256(canUnwrap); | ||
|
||
uint256 requestId = Gateway.requestDecryption( | ||
cts, | ||
this.callbackUnwrap.selector, | ||
0, | ||
block.timestamp + 100, | ||
false | ||
); | ||
|
||
unwrapRequests[requestId] = UnwrapRequest({ account: msg.sender, amount: amount }); | ||
} | ||
|
||
/** | ||
* @notice Wrap ERC20 tokens to an encrypted format. | ||
* @param amount Amount to wrap. | ||
*/ | ||
function wrap(uint256 amount) public virtual { | ||
ERC20_TOKEN.safeTransferFrom(msg.sender, address(this), amount); | ||
|
||
uint256 amountAdjusted = amount / (10 ** (ERC20_TOKEN.decimals() - decimals())); | ||
|
||
if (amountAdjusted > type(uint64).max) { | ||
revert AmountTooHigh(); | ||
} | ||
|
||
uint64 amountUint64 = uint64(amountAdjusted); | ||
|
||
_unsafeMint(msg.sender, amountUint64); | ||
_totalSupply += amountUint64; | ||
|
||
emit Wrap(msg.sender, amountUint64); | ||
} | ||
|
||
/** | ||
* @notice Callback function for the gateway. | ||
* @param requestId Request id. | ||
* @param canUnwrap Whether it can be unwrapped. | ||
*/ | ||
function callbackUnwrap(uint256 requestId, bool canUnwrap) public virtual nonReentrant onlyGateway { | ||
UnwrapRequest memory unwrapRequest = unwrapRequests[requestId]; | ||
|
||
if (canUnwrap) { | ||
/// @dev It does a supply adjustment. | ||
uint256 amountUint256 = unwrapRequest.amount * (10 ** (ERC20_TOKEN.decimals() - decimals())); | ||
|
||
try ERC20_TOKEN.transfer(unwrapRequest.account, amountUint256) { | ||
_unsafeBurn(unwrapRequest.account, unwrapRequest.amount); | ||
_totalSupply -= unwrapRequest.amount; | ||
emit Unwrap(unwrapRequest.account, unwrapRequest.amount); | ||
} catch { | ||
emit UnwrapFailTransferFail(unwrapRequest.account, unwrapRequest.amount); | ||
} | ||
} else { | ||
emit UnwrapFailNotEnoughBalance(unwrapRequest.account, unwrapRequest.amount); | ||
} | ||
|
||
delete unwrapRequests[requestId]; | ||
delete isAccountRestricted[unwrapRequest.account]; | ||
} | ||
|
||
function _canTransferOrUnwrap(address account) internal virtual { | ||
if (isAccountRestricted[account]) { | ||
revert CannotTransferOrUnwrap(); | ||
} | ||
} | ||
|
||
function _transferNoEvent( | ||
address from, | ||
address to, | ||
euint64 amount, | ||
ebool isTransferable | ||
) internal virtual override { | ||
_canTransferOrUnwrap(from); | ||
super._transferNoEvent(from, to, amount, isTransferable); | ||
} | ||
|
||
function _unsafeBurn(address account, uint64 amount) internal { | ||
euint64 newBalanceAccount = TFHE.sub(_balances[account], amount); | ||
_balances[account] = newBalanceAccount; | ||
TFHE.allowThis(newBalanceAccount); | ||
TFHE.allow(newBalanceAccount, account); | ||
emit Transfer(account, address(0), _PLACEHOLDER); | ||
} | ||
} |
Oops, something went wrong.