mirror of
git://holbrook.no/eth-faucet
synced 2025-01-09 02:27:32 +01:00
220 lines
5.7 KiB
Solidity
220 lines
5.7 KiB
Solidity
pragma solidity >=0.8.0;
|
|
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
contract EthFaucet {
|
|
|
|
// Implements ERC173
|
|
address public owner;
|
|
address public registry;
|
|
address public periodChecker;
|
|
|
|
// Implements Faucet
|
|
address constant public token = address(0);
|
|
|
|
// Implements Seal
|
|
uint256 public sealState;
|
|
|
|
uint256 amount;
|
|
|
|
uint8 constant REGISTRY_STATE = 1;
|
|
uint8 constant PERIODCHECKER_STATE = 2;
|
|
uint8 constant VALUE_STATE = 4;
|
|
// Implements Seal
|
|
uint256 constant public maxSealState = 7;
|
|
|
|
// Implements Faucet
|
|
event Give(address indexed _recipient, address indexed _token, uint256 _amount);
|
|
// Implements Faucet
|
|
event FaucetAmountChange(uint256 _amount);
|
|
|
|
// Implements Seal
|
|
event SealStateChange(uint256 indexed _sealState, address _registry, address _periodChecker);
|
|
|
|
constructor() {
|
|
owner = msg.sender;
|
|
}
|
|
|
|
// Set the given seal bits.
|
|
// Reverts if any bits are already set, if bit value is out of bounds.
|
|
function seal(uint256 _state) public returns(uint256) {
|
|
require(_state < 8, 'ERR_INVALID_STATE');
|
|
require(_state & sealState == 0, 'ERR_ALREADY_LOCKED');
|
|
sealState |= _state;
|
|
emit SealStateChange(sealState, registry, periodChecker);
|
|
return uint256(sealState);
|
|
}
|
|
|
|
// Change faucet amount.
|
|
// Reverts if VALUE_STATE seal is set.
|
|
function setAmount(uint256 _v) public returns(uint256) {
|
|
require(msg.sender == owner, 'ERR_NOT_OWNER');
|
|
require(sealState & VALUE_STATE == 0, 'ERR_SEALED');
|
|
amount = _v;
|
|
emit FaucetAmountChange(amount);
|
|
return amount;
|
|
}
|
|
|
|
// Set period checker contract backend.
|
|
// Reverts if PERIODCHECKER_STATE seal is set
|
|
function setPeriodChecker(address _checker) public {
|
|
require(msg.sender == owner, 'ERR_NOT_OWNER');
|
|
require(sealState & PERIODCHECKER_STATE == 0, 'ERR_SEALED');
|
|
periodChecker = _checker;
|
|
emit SealStateChange(sealState, registry, periodChecker);
|
|
}
|
|
|
|
// Set accounts index (Access Control List - ACL) backend.
|
|
// Reverts if REGISTRY_STATE seal is set
|
|
function setRegistry(address _registry) public {
|
|
require(msg.sender == owner, 'ERR_NOT_OWNER');
|
|
require(sealState & REGISTRY_STATE == 0, 'ERR_SEALED');
|
|
registry = _registry;
|
|
emit SealStateChange(sealState, registry, periodChecker);
|
|
}
|
|
|
|
// Return true if period checker backend allows usage of the faucet.
|
|
// Will always return true if period checker contract address has not been set.
|
|
function checkPeriod(address _recipient) private returns(bool) {
|
|
bool _ok;
|
|
bytes memory _result;
|
|
|
|
if (periodChecker == address(0)) {
|
|
return true;
|
|
}
|
|
|
|
(_ok, _result) = periodChecker.call(abi.encodeWithSignature("check(address)", _recipient));
|
|
if (!_ok) {
|
|
revert('ERR_PERIOD_BACKEND');
|
|
}
|
|
return _result[31] == 0x01;
|
|
}
|
|
|
|
// Return true if recipient has been added to the ACL.
|
|
// Will always return true if ACL contract address has not been set.
|
|
function checkRegistry(address _recipient) private returns(bool) {
|
|
bool _ok;
|
|
bytes memory _result;
|
|
|
|
if (registry == address(0)) {
|
|
return true;
|
|
}
|
|
|
|
(_ok, _result) = registry.call(abi.encodeWithSignature("have(address)", _recipient));
|
|
if (!_ok) {
|
|
revert('ERR_REGISTRY_BACKEND');
|
|
}
|
|
return _result[31] == 0x01;
|
|
}
|
|
|
|
// Return false if contract does not have sufficient gas token balance to cover a single use.
|
|
// Used as backend for check.
|
|
function checkBalance() private view returns(bool) {
|
|
return amount <= address(this).balance;
|
|
}
|
|
|
|
// Check if a faucet usage attempt would succeed for the given recipient in the current contract state.
|
|
function check(address _recipient) public returns(bool) {
|
|
if (!checkPeriod(_recipient)) {
|
|
return false;
|
|
}
|
|
if (!checkRegistry(_recipient)) {
|
|
return false;
|
|
}
|
|
return checkBalance();
|
|
}
|
|
|
|
// Execute a single faucet usage for recipient.
|
|
function checkAndPoke(address _recipient) private returns(bool){
|
|
bool _ok;
|
|
bytes memory _result;
|
|
|
|
if (!checkBalance()) {
|
|
revert('ERR_INSUFFICIENT_BALANCE');
|
|
}
|
|
|
|
if (!checkRegistry(_recipient)) {
|
|
revert('ERR_NOT_IN_WHITELIST');
|
|
}
|
|
|
|
if (periodChecker == address(0)) {
|
|
return true;
|
|
}
|
|
|
|
(_ok, _result) = periodChecker.call(abi.encodeWithSignature("poke(address)", _recipient));
|
|
if (!_ok) {
|
|
revert('ERR_PERIOD_BACKEND');
|
|
}
|
|
if (_result[31] == 0) {
|
|
revert('ERR_PERIOD_CHECK');
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Implements Faucet
|
|
function gimme() public returns(uint256) {
|
|
require(checkAndPoke(msg.sender));
|
|
payable(msg.sender).transfer(amount);
|
|
emit Give(msg.sender, address(0), amount);
|
|
return amount;
|
|
}
|
|
|
|
// Implements Faucet
|
|
function giveTo(address _recipient) public returns(uint256) {
|
|
require(checkAndPoke(_recipient));
|
|
payable(_recipient).transfer(amount);
|
|
emit Give(_recipient, address(0), amount);
|
|
return amount;
|
|
}
|
|
|
|
// Implements Faucet
|
|
function nextTime(address _subject) public returns(uint256) {
|
|
bool _ok;
|
|
bytes memory _result;
|
|
|
|
(_ok, _result) = periodChecker.call(abi.encodeWithSignature("next(address)", _subject));
|
|
if (!_ok) {
|
|
revert('ERR_PERIOD_BACKEND_ERROR');
|
|
}
|
|
return uint256(bytes32(_result));
|
|
}
|
|
|
|
// Implements Faucet
|
|
function nextBalance(address _subject) public returns(uint256) {
|
|
bool _ok;
|
|
bytes memory _result;
|
|
|
|
(_ok, _result) = periodChecker.call(abi.encodeWithSignature("balanceThreshold()", _subject));
|
|
if (!_ok) {
|
|
revert('ERR_PERIOD_BACKEND_ERROR');
|
|
}
|
|
return uint256(bytes32(_result));
|
|
|
|
}
|
|
|
|
// Implements Faucet
|
|
function tokenAmount() public view returns(uint256) {
|
|
return amount;
|
|
}
|
|
|
|
receive () payable external {
|
|
}
|
|
|
|
// Implements ERC165
|
|
function supportsInterface(bytes4 _sum) public pure returns (bool) {
|
|
if (_sum == 0x01ffc9a7) { // ERC165
|
|
return true;
|
|
}
|
|
if (_sum == 0x9493f8b2) { // ERC173
|
|
return true;
|
|
}
|
|
if (_sum == 0x1a3ac634) { // Faucet
|
|
return true;
|
|
}
|
|
if (_sum == 0x0d7491f8) { // Seal
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|