eth-faucet

Gas token gifter with controls from time intervals, amounts and access
Info | Log | Files | Refs | README

EthFaucet.sol (5806B)


      1 pragma solidity >=0.8.0;
      2 
      3 // SPDX-License-Identifier: AGPL-3.0-or-later
      4 
      5 contract EthFaucet {
      6 
      7 	// Implements ERC173
      8 	address public owner;
      9 	address public registry;
     10 	address public periodChecker;
     11 
     12 	// Implements Faucet
     13 	address constant public token = address(0);
     14 
     15 	// Implements Seal
     16 	uint256 public sealState;
     17 
     18 	uint256 amount;
     19 
     20 	uint8 constant REGISTRY_STATE = 1;
     21 	uint8 constant PERIODCHECKER_STATE = 2;
     22 	uint8 constant VALUE_STATE = 4;
     23 	// Implements Seal
     24 	uint256 constant public maxSealState = 7;
     25 
     26 	// Implements Faucet
     27 	event Give(address indexed _recipient, address indexed _token, uint256 _amount);
     28 	// Implements Faucet
     29 	event FaucetAmountChange(uint256 _amount);
     30 
     31 	// Implements Seal
     32 	event SealStateChange(uint256 indexed _sealState, address _registry, address _periodChecker);
     33 
     34 	constructor() {
     35 		owner = msg.sender;
     36 	}
     37 
     38 	// Set the given seal bits.
     39 	// Reverts if any bits are already set, if bit value is out of bounds.
     40 	function seal(uint256 _state) public returns(uint256) {
     41 		require(_state < 8, 'ERR_INVALID_STATE');
     42 		require(_state & sealState == 0, 'ERR_ALREADY_LOCKED');
     43 		sealState |= _state;
     44 		emit SealStateChange(sealState, registry, periodChecker);
     45 		return uint256(sealState);
     46 	}
     47 
     48 	// Change faucet amount.
     49 	// Reverts if VALUE_STATE seal is set.
     50 	function setAmount(uint256 _v) public returns(uint256) {
     51 		require(msg.sender == owner, 'ERR_NOT_OWNER');
     52 		require(sealState & VALUE_STATE == 0, 'ERR_SEALED');
     53 		amount = _v;
     54 		emit FaucetAmountChange(amount);
     55 		return amount;
     56 	}
     57 
     58 	// Set period checker contract backend.
     59 	// Reverts if PERIODCHECKER_STATE seal is set
     60 	function setPeriodChecker(address _checker) public {
     61 		require(msg.sender == owner, 'ERR_NOT_OWNER');
     62 		require(sealState & PERIODCHECKER_STATE == 0, 'ERR_SEALED');
     63 		periodChecker = _checker;
     64 		emit SealStateChange(sealState, registry, periodChecker);
     65 	}
     66 
     67 	// Set accounts index (Access Control List - ACL) backend.
     68 	// Reverts if REGISTRY_STATE seal is set
     69 	function setRegistry(address _registry) public {
     70 		require(msg.sender == owner, 'ERR_NOT_OWNER');
     71 		require(sealState & REGISTRY_STATE == 0, 'ERR_SEALED');
     72 		registry = _registry;
     73 		emit SealStateChange(sealState, registry, periodChecker);
     74 	}
     75 
     76 	// Return true if period checker backend allows usage of the faucet.
     77 	// Will always return true if period checker contract address has not been set.
     78 	function checkPeriod(address _recipient) private returns(bool) {
     79 		bool _ok;
     80 		bytes memory _result;
     81 
     82 		if (periodChecker == address(0)) {
     83 			return true;
     84 		}
     85 
     86 		(_ok, _result) = periodChecker.call(abi.encodeWithSignature("have(address)", _recipient));
     87 		if (!_ok) {
     88 			revert('ERR_PERIOD_BACKEND');
     89 		}
     90 		return _result[31] == 0x01;
     91 	}
     92 
     93 	// Return true if recipient has been added to the ACL.
     94 	// Will always return true if ACL contract address has not been set.
     95 	function checkRegistry(address _recipient) private returns(bool) {
     96 		bool _ok;
     97 		bytes memory _result;
     98 
     99 		if (registry == address(0)) {
    100 			return true;
    101 		}
    102 
    103 		(_ok, _result) = registry.call(abi.encodeWithSignature("have(address)", _recipient));
    104 		if (!_ok) {
    105 			revert('ERR_REGISTRY_BACKEND');
    106 		}
    107 		return _result[31] == 0x01;
    108 	}
    109 
    110 	// Return false if contract does not have sufficient gas token balance to cover a single use.
    111 	// Used as backend for check.
    112 	function checkBalance() private view returns(bool) {
    113 		return amount <= address(this).balance;
    114 	}
    115 
    116 	// Check if a faucet usage attempt would succeed for the given recipient in the current contract state.
    117 	function check(address _recipient) public returns(bool) {
    118 		if (!checkPeriod(_recipient)) {
    119 			return false;
    120 		}
    121 		if (!checkRegistry(_recipient)) {
    122 			return false;
    123 		}
    124 		return checkBalance();
    125 	}
    126 
    127 	// Execute a single faucet usage for recipient.
    128 	function checkAndPoke(address _recipient) private returns(bool){
    129 		bool _ok;
    130 		bytes memory _result;
    131 
    132 		if (!checkBalance()) {
    133 			revert('ERR_INSUFFICIENT_BALANCE');
    134 		}
    135 
    136 		if (!checkRegistry(_recipient)) {
    137 			revert('ERR_NOT_IN_WHITELIST');
    138 		}
    139 
    140 		if (periodChecker == address(0)) {
    141 			return true;
    142 		}
    143 
    144 		(_ok, _result) = periodChecker.call(abi.encodeWithSignature("poke(address)", _recipient));
    145 		if (!_ok) {
    146 			revert('ERR_PERIOD_BACKEND');
    147 		} 
    148 		if (_result[31] == 0) {
    149 			revert('ERR_PERIOD_CHECK');
    150 		}
    151 		return true;
    152 	}
    153 
    154 	// Implements Faucet
    155 	function gimme() public returns(uint256) {
    156 		require(checkAndPoke(msg.sender));
    157 		payable(msg.sender).transfer(amount);
    158 		emit Give(msg.sender, address(0), amount);
    159 		return amount;
    160 	}
    161 
    162 	// Implements Faucet
    163 	function giveTo(address _recipient) public returns(uint256) {
    164 		require(checkAndPoke(_recipient));
    165 		payable(_recipient).transfer(amount);
    166 		emit Give(_recipient, address(0), amount);
    167 		return amount;
    168 	}
    169 
    170 	// Implements Faucet
    171 	function nextTime(address _subject) public returns(uint256) {
    172 		bool _ok;
    173 		bytes memory _result;
    174 
    175 		(_ok, _result) = periodChecker.call(abi.encodeWithSignature("next(address)", _subject));
    176 		if (!_ok) {
    177 			revert('ERR_PERIOD_BACKEND_ERROR');
    178 		}
    179 		return uint256(bytes32(_result));
    180 	}
    181 
    182 	// Implements Faucet
    183 	function nextBalance(address _subject) public returns(uint256) {
    184 		bool _ok;
    185 		bytes memory _result;
    186 
    187 		(_ok, _result) = periodChecker.call(abi.encodeWithSignature("balanceThreshold()", _subject));
    188 		if (!_ok) {
    189 			revert('ERR_PERIOD_BACKEND_ERROR');
    190 		}
    191 		return uint256(bytes32(_result));
    192 
    193 	}
    194 
    195 	// Implements Faucet
    196 	function tokenAmount() public view returns(uint256) {
    197 		return amount;
    198 	}
    199 
    200 	receive () payable external {
    201 	}
    202 
    203 	// Implements ERC165
    204 	function supportsInterface(bytes4 _sum) public pure returns (bool) {
    205 		if (_sum == 0x01ffc9a7) { // ERC165
    206 			return true;
    207 		}
    208 		if (_sum == 0x9493f8b2) { // ERC173
    209 			return true;
    210 		}
    211 		if (_sum == 0x1a3ac634) { // Faucet
    212 			return true;
    213 		}
    214 		if (_sum == 0x0d7491f8) { // Seal
    215 			return true;
    216 		}
    217 		return false;
    218 	}
    219 }