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