Skip to content

Commit ff76fb8

Browse files
committed
add ENS tools
1 parent 63452c1 commit ff76fb8

File tree

3 files changed

+372
-0
lines changed

3 files changed

+372
-0
lines changed

basic/61-ENS/contracts/push2ENS.sol

+303
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
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+
}

basic/61-ENS/contracts/supportENS.sol

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// This is a quick and small library for allowing any contract to
2+
// use ENS names like they would use a standard address.
3+
// So a DAO can have bob.eth as the member, instead of 0x123,
4+
// and therefore Bob can change that address to whatever they prefer
5+
// or give out a tip that can be only received by the rightful owner of example.com
6+
// even if example.com owner never heard about ethereum!
7+
//
8+
// To use it, just use a store the NameHash as a string and then call
9+
// updateAddress(NameHash) to save the latest ethereum address it points to
10+
// Once that has been saved, use ensAddresses(NameHash) to obtain a valid eth address.
11+
// Optionally, you can check lastUpdatedENSAt(NameHash) to check the last time
12+
// it was updated. If it was never updated then both functions should return 0:
13+
// IMPORTANT: check if address is not address(0) before sending ether.
14+
15+
// Learn more about NameHash: https://docs.ens.domains/contract-api-reference/name-processing
16+
// Learn more about full DNS support in ENS: https://medium.com/the-ethereum-name-service/full-dns-namespace-integration-to-ens-now-on-mainnet-9d37270807d3
17+
// Learn more about ENS: ens.domains
18+
19+
// Author: Alex Van de Sande
20+
// License: Public Domain
21+
22+
pragma solidity ^0.8.0;
23+
24+
abstract contract ENS {
25+
function resolver(string memory node) public virtual view returns (Resolver);
26+
}
27+
28+
abstract contract Resolver {
29+
function addr(string memory node) public virtual view returns (address);
30+
}
31+
32+
contract SupportsENS {
33+
// Same address for Mainet, Ropsten, Rinkerby, Gorli and other networks;
34+
ENS ens = ENS(0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e);
35+
36+
// Optional modifier to only allow calls from a specific ENS owner
37+
modifier onlyENSOwner(string memory nameHash) {
38+
require(
39+
msg.sender == getENSAddress(nameHash),
40+
"Only ENS owner can call this contract"
41+
);
42+
_;
43+
}
44+
45+
// Main function to get an address
46+
// ATTENTION: if an ENS is not set or claimed, it will return 0x0.
47+
// Make sure to check this before sending ether.
48+
function getENSAddress(string memory nameHash) public view returns (address){
49+
Resolver resolver = ens.resolver(nameHash);
50+
return resolver.addr(nameHash);
51+
}
52+
53+
// Similar to the function above but will
54+
// revert if the address returns 0x00..
55+
function getSafeENSAddress(string memory nameHash) public view returns (address){
56+
Resolver resolver = ens.resolver(nameHash);
57+
address res = resolver.addr(nameHash);
58+
require(res != address(0), "address not set or set to burn");
59+
return res;
60+
}
61+
62+
}

basic/61-ENS/readme.md

+7
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ ENS(Ethereum Name Service)是以太坊域名服务,是一个基于以太
66

77
ENS的工作是将可读的域名(比如"alice.eth")解析为计算机可以识别的标识符,如以太坊地址、内容的散列、元数据等。ENS还支持"反向解析",这使得将元数据(如规范化域名或接口描述)与以太坊地址相关联成为可能
88

9+
### ENS tools
10+
11+
- supportENS.sol
12+
- push2ENS.sol
13+
14+
Refer to https://github.com/alexvansande/ENSTools
15+
916
### 使用流程
1017

1118
ENS JavaScript 库:

0 commit comments

Comments
 (0)