1
+ // SPDX-License-Identifier: UNLICENSED
2
+
3
+ // ____ _ _ ____ _ _ ____ ____ __ _ ____
4
+ // ( _ \/ )( \/ ___)/ )( \ (___ \ ( __)( ( \/ ___)
5
+ // ) __/) \/ (\___ \) __ ( / __/ ) _) / /\___ \
6
+ // (__) \____/(____/\_)(_/ (____) (____)\_)__)(____/
7
+ //
8
+ // By Alex Van de Sande
9
+ //
10
+ // A little contract that adds push & pull money transfer to ENS ensAddresses.
11
+ // Will work with any ENS, existing or future, including DNS names!
12
+ // Ether, tokens and NFTs remain locked by contract until ENS is set up properly.
13
+ // Once ENS points to a non-zero address, anyone can pull it there.
14
+ //
15
+ // Want to donate USDC to wikipedia.org but they don't have an eth address yet?
16
+ // Want to send an NFT to xkcd.org but you're not sure how to trust the address?
17
+ // Want to make an Ether bounty for the Ethiopian government to be aware of ENS?
18
+ // Now you can do all that and more!
19
+ // But wait, there's more! You can also use it to push to a normal address.
20
+ // Just because.
21
+ //
22
+ // ATTENTION: use at your own risk! I've barely tested it. And by barely I mean I tried a couple different things
23
+ // on rinkeby and once they worked I deployed to mainnet. NFTs don't use _safeTransfer
24
+ // because I was too lazy to build my own onerc721Received. Be careful out there.
25
+
26
+ pragma solidity ^ 0.8.0 ;
27
+ /// @title Push to ENS
28
+ /// @author Alex Van de Sande
29
+ /// @notice Allows tokens and ether to be deposited to ENS names
30
+ /// @dev Supports ether, erc20 tokens and erc721 NFTs.
31
+
32
+ import "./supportsENS.sol " ;
33
+
34
+ // works for both Tokens and NFTs
35
+ abstract contract Transferable {
36
+ function transferFrom (address from , address to , uint tokens ) virtual public ;
37
+ }
38
+
39
+ contract PushToENS is SupportsENS {
40
+
41
+ /// @notice Internal balance function
42
+ /// @dev Balances are a hash based on token and account information
43
+ mapping (bytes32 => Balance) public balances;
44
+
45
+ struct Balance {
46
+ uint256 withdrawable;
47
+ uint256 lastBlockPulled;
48
+ mapping (address => uint256 ) pusher;
49
+ mapping (address => uint256 ) lastPushed;
50
+ }
51
+
52
+ event Pushed (bytes32 nameHash , address assetAddress , uint8 sendType , uint256 amount );
53
+ event Pulled (bytes32 nameHash , address assetAddress , uint8 sendType , uint256 amount );
54
+ event Cancel (bytes32 nameHash , address assetAddress , uint8 sendType , uint256 amount );
55
+
56
+ // PUSH FUNCTIONS
57
+
58
+ /**
59
+ * @dev Generic internal push functions
60
+ */
61
+ function _push (bytes32 hash , uint256 amount , address assetAddress , uint8 sendType ) internal {
62
+ Balance storage balance = balances[hash];
63
+
64
+ // If there was a pull since you last pushed, then you can't withdraw that amount anymore
65
+ if (balance.lastPushed[msg .sender ] < balance.lastBlockPulled) balance.pusher[msg .sender ]= 0 ;
66
+
67
+ // Weird things can happen in the same block
68
+ require (balance.lastPushed[msg .sender ] != balance.lastBlockPulled || balance.lastBlockPulled == 0 , "You can't push the same block on a pull " );
69
+
70
+ // Prevent Overflows
71
+ require (balance.pusher[msg .sender ] + amount > balance.pusher[msg .sender ] , "Overflow detected! " );
72
+ require (balance.withdrawable + amount > balance.withdrawable , "Overflow detected! " );
73
+
74
+ // Increase the balances
75
+ balance.withdrawable += amount;
76
+ balance.pusher[msg .sender ]+= amount;
77
+ balance.lastPushed[msg .sender ] = block .number ;
78
+
79
+ // tell everyone about it
80
+ emit Pushed (hash, assetAddress, sendType, amount);
81
+ }
82
+
83
+ /**
84
+ * @notice Pushes ether sent in transaction for an ENS name represented by `nameHash`
85
+ * @param nameHash a 256-bit hash that represents an ENS name. See: https://docs.ens.domains/contract-api-reference/name-processing
86
+ */
87
+ function pushEther2ENS (bytes32 nameHash ) public payable {
88
+ _push (nameHash, msg .value , 0x0000000000000000000000000000000000000000 , 0 );
89
+ }
90
+
91
+ /**
92
+ * @notice Pushes ether sent in transaction for `ethAddress`
93
+ * @param ethAddress a standard ethereum compatible address
94
+ */
95
+ function pushEther2Ethadd (address ethAddress ) public payable {
96
+ _push (keccak256 (abi.encode (ethAddress)), msg .value , 0x0000000000000000000000000000000000000000 , 1 );
97
+ }
98
+
99
+ /**
100
+ * @notice Pushes `amount` standard units of the `assetAddress` token for an ENS address represented by `nameHash`.
101
+ * @notice If `nftId` is set, then it assumes it's a ERC721 NFT and sends the one with `nftId`.
102
+ * @dev If it's a token, leave nftId as 0. Can't send any NFT with id 0.
103
+ * @param nameHash a 256-bit hash that represents an ENS name. See: https://docs.ens.domains/contract-api-reference/name-processing
104
+ * @param assetAddress The address of either the token or NFT
105
+ * @param amount Token amount. If it's an NFT, leave 0.
106
+ * @param nftId the id of the NFT being sent. If it's a token, leave 0.
107
+ */
108
+ function pushAsset2ENS (bytes32 nameHash , address assetAddress , uint256 amount , uint256 nftId ) public {
109
+ // instantiate token
110
+ Transferable asset = Transferable (assetAddress);
111
+ uint256 wat;
112
+
113
+ if (nftId== 0 ) {
114
+ // If fungible, then add all balances
115
+ wat = amount;
116
+ _push (keccak256 (abi.encode (nameHash,assetAddress)), amount, assetAddress, 2 );
117
+ } else {
118
+ // If not, then use amount as the nft ID
119
+ wat = nftId;
120
+ _push (keccak256 (abi.encode (nameHash,assetAddress,nftId)), nftId, assetAddress, 3 );
121
+ }
122
+
123
+ // transfer assets. Doesn't add a require, assumes token will revert if fails
124
+ asset.transferFrom (msg .sender , address (this ), wat);
125
+ }
126
+
127
+ // PULL FUNCTIONS
128
+
129
+ /**
130
+ * @dev Generic internal pull function
131
+ */
132
+ function _pull (bytes32 hash , address assetAddress , uint8 sendType ) internal returns (uint256 amount ){
133
+ Balance storage balance = balances[hash];
134
+
135
+ amount = balance.withdrawable;
136
+
137
+ //Pull full amount always
138
+ balance.withdrawable= 0 ;
139
+ balance.lastBlockPulled = block .number ;
140
+
141
+ // Interaction
142
+ emit Pulled (hash, assetAddress, sendType, amount );
143
+
144
+ return amount;
145
+ }
146
+
147
+ /**
148
+ * @notice Pulls ether for any valid ENS name.
149
+ * @dev Function can be called by anyone. Will revert if ENS name is not set, or set to 0x.
150
+ * @param nameHash a 256-bit hash that represents an ENS name. See: https://docs.ens.domains/contract-api-reference/name-processing
151
+ */
152
+ function pullEther2ENS (bytes32 nameHash ) public {
153
+ // Zeroes out full balance
154
+ uint256 amount = _pull (nameHash, 0x0000000000000000000000000000000000000000 , 0 );
155
+
156
+ //Interaction
157
+ // getSafeENSAddress prevents returning empty address
158
+ sendValue (payable (getSafeENSAddress (nameHash)), amount);
159
+ }
160
+
161
+ /**
162
+ * @notice Pulls all ether pushed for your address.
163
+ * @dev Can only be called by the address receiving the token.
164
+ */
165
+ function pullEther2Ethadd () public {
166
+ // Zeroes out full balance
167
+ uint256 amount = _pull (keccak256 (abi.encode (msg .sender )), 0x0000000000000000000000000000000000000000 , 1 );
168
+
169
+ //Interaction
170
+ sendValue (payable (msg .sender ), amount);
171
+ }
172
+
173
+ /**
174
+ * @notice Pulls all of the balance from token `assetAddress` for the ENS address represented by `nameHash`.
175
+ * @notice If `nftId` is set, then it assumes it's a NFT and pulls the one with `nftId`.
176
+ * @dev Can be called by anyone. If it's a token, leave nftId as 0. Can only pull full amounts.
177
+ * @dev Function will revert if ENS is not set or set to 0x0.
178
+ * @param nameHash a 256-bit hash that represents an ENS name. See: https://docs.ens.domains/contract-api-reference/name-processing
179
+ * @param assetAddress The address of either the token or NFT
180
+ * @param nftId the id of the NFT being sent. If it's a token, leave 0.
181
+ */
182
+ function pullAsset2ENS (bytes32 nameHash , address assetAddress , uint nftId ) public {
183
+ // instantiate token
184
+ Transferable asset = Transferable (assetAddress);
185
+ uint256 wat;
186
+ // If it's a token then use nftId as 0
187
+ if (nftId== 0 ){
188
+ //if ID is 0 then it's a token
189
+ wat = _pull (keccak256 (abi.encode (nameHash,assetAddress)), assetAddress, 2 );
190
+ } else {
191
+ //if ID is set then it's an NFT
192
+ wat = nftId;
193
+ _pull (keccak256 (abi.encode (nameHash, assetAddress, nftId)), assetAddress, 3 );
194
+ }
195
+
196
+ // getSafeENSAddress prevents returning empty address
197
+ asset.transferFrom (address (this ), getSafeENSAddress (nameHash), wat);
198
+ }
199
+
200
+ // CANCEL FUNCTIONS
201
+
202
+ /**
203
+ * @dev Generic internal cancel function
204
+ */
205
+ function _cancel (bytes32 hash , uint256 amount , address assetAddress , uint8 sendType ) internal {
206
+ Balance storage balance = balances[hash];
207
+
208
+ // Can only cancel if there wasn't a push yet
209
+ require (balance.lastPushed[msg .sender ] > balance.lastBlockPulled || balance.lastBlockPulled == 0 , "Recipient already withdrew " );
210
+
211
+ // Check if enough balance
212
+ require (amount <= balance.pusher[msg .sender ] , "Not enough balance " );
213
+ require (amount <= balance.withdrawable , "Not enough balance " );
214
+
215
+ // Decrease both balances
216
+ balance.pusher[msg .sender ]-= amount;
217
+ balance.withdrawable -= amount;
218
+
219
+ // Interaction
220
+ emit Cancel (hash, assetAddress, sendType, amount );
221
+ }
222
+
223
+ /**
224
+ * @dev Get back `amount/1000000000000000000` ether you had pushed to an ENS name but it hasn't pulled yet.
225
+ * @param nameHash a 256-bit hash that represents an ENS name. See: https://docs.ens.domains/contract-api-reference/name-processing
226
+ * @param amount Ether amount you want to get back.
227
+ */
228
+ function cancelEther2ENS (bytes32 nameHash , uint256 amount ) public {
229
+ // Generic cancel function
230
+ _cancel (nameHash, amount, 0x0000000000000000000000000000000000000000 , 0 );
231
+
232
+ //Interaction
233
+ // getSafeENSAddress prevents returning empty address
234
+ sendValue (payable (msg .sender ), amount);
235
+ }
236
+
237
+
238
+ /**
239
+ * @dev Get back `amount/1000000000000000000` ether you had pushed to an Eth address name but it hasn't pulled yet.
240
+ * @param ethAddress The address which you had pushed to.
241
+ * @param amount Ether amount you want to get back.
242
+ */
243
+ function cancelEther2Ethadd (address ethAddress , uint256 amount ) public {
244
+ // Generic cancel function
245
+ _cancel (keccak256 (abi.encode (ethAddress)), amount, 0x0000000000000000000000000000000000000000 , 1 );
246
+
247
+ //Interaction
248
+ sendValue (payable (msg .sender ), amount);
249
+ }
250
+
251
+ /**
252
+ * @notice Get back tokens or NFT you had pushed to an ENS name but hasn't been claimed yet.
253
+ * @param nameHash a 256-bit hash that represents an ENS name. See: https://docs.ens.domains/contract-api-reference/name-processing
254
+ * @param assetAddress The address of either the token or NFT
255
+ * @param amount Amount you want to pull back
256
+ * @param nftId the id of the NFT being sent. If it's a token, leave 0.
257
+ */
258
+ function cancelAsset2ENS (bytes32 nameHash , address assetAddress , uint256 amount , uint256 nftId ) public {
259
+ // instantiate token
260
+ Transferable asset = Transferable (assetAddress);
261
+ uint256 wat;
262
+ //generic cancel
263
+ // getSafeENSAddress prevents returning empty address
264
+ if (nftId== 0 ) {
265
+ // If it's a token then pull all of them
266
+ wat= amount;
267
+ _cancel (keccak256 (abi.encode (nameHash,assetAddress)), amount, assetAddress, 2 );
268
+ } else {
269
+ // if it's an NFT use amount as tokenId
270
+ wat= nftId;
271
+ _cancel (keccak256 (abi.encode (nameHash, assetAddress, nftId)), nftId, assetAddress, 3 );
272
+ }
273
+
274
+ // transfer tokens
275
+ asset.transferFrom (address (this ), msg .sender , wat);
276
+
277
+ }
278
+
279
+
280
+ /**
281
+ * Open Zeppellin's Send Value
282
+ * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
283
+ * `recipient`, forwarding all available gas and reverting on errors.
284
+ *
285
+ * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
286
+ * of certain opcodes, possibly making contracts go over the 2300 gas limit
287
+ * imposed by `transfer`, making them unable to receive funds via
288
+ * `transfer`. {sendValue} removes this limitation.
289
+ *
290
+ * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
291
+ *
292
+ * IMPORTANT: because control is transferred to `recipient`, care must be
293
+ * taken to not create reentrancy vulnerabilities. Consider using
294
+ * {ReentrancyGuard} or the
295
+ * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
296
+ */
297
+ function sendValue (address payable recipient , uint256 amount ) internal {
298
+ require (address (this ).balance >= amount, "Address: insufficient balance " );
299
+
300
+ (bool success , ) = recipient.call {value: amount}("" );
301
+ require (success, "Address: unable to send value, recipient may have reverted " );
302
+ }
303
+ }
0 commit comments