-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathConfidentialERC20Wrapped.sol
163 lines (137 loc) · 6.29 KB
/
ConfidentialERC20Wrapped.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
// SPDX-License-Identifier: BSD-3-Clause-Clear
pragma solidity ^0.8.24;
import "fhevm/lib/TFHE.sol";
import "fhevm/gateway/GatewayCaller.sol";
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 { 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 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;
/**
* @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);
}
}