Skip to content

Commit

Permalink
Merge pull request #3 from subspace/improve-cleanup-faucet-contracts
Browse files Browse the repository at this point in the history
Improve cleanup faucet smart contracts
  • Loading branch information
marc-aurele-besner authored Sep 13, 2023
2 parents 7b931d3 + 292a7d4 commit 551133b
Show file tree
Hide file tree
Showing 35 changed files with 5,002 additions and 4,889 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/foundry-npm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Run Foundry Test with NPM

on: [push]

jobs:
test_foundry_npm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v3
with:
node-version: 16
- name: Install Foundry
uses: onbjerg/foundry-toolchain@v1
with:
version: nightly
- name: NPM Clean Install
run: cd smart_contract && npm ci
- name: Run Forge Test
run: forge test
19 changes: 19 additions & 0 deletions .github/workflows/hardhat-npm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Run Hardhat Test with NPM

on:
push:
workflow_dispatch:

jobs:
test_hardhat_npm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: NPM Clean Install
run: cd smart_contract && npm ci
- name: Hardhat Compile
run: cd smart_contract && npx hardhat compile
- name: Hardhat Test
run: cd smart_contract && npx hardhat test
- name: Hardhat Coverage Result
run: cd smart_contract && npx hardhat coverage
10 changes: 10 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"recommendations": [
"JuanBlanco.solidity",
"tintinweb.solidity-visual-auditor",
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"eamodio.gitlens",
"streetsidesoftware.code-spell-checker"
]
}
29 changes: 29 additions & 0 deletions .vscode/faucet.code-workspace
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"folders": [
{
"name": "root",
"path": ".."
},
{
"name": "discord-bot",
"path": "../discord-botp"
},
{
"name": "smart-contracts",
"path": "../smart_contract"
}
],
"settings": {
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.fixAll.tslint": true
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"eslint.workingDirectories": [{ "mode": "auto" }],
"javascript.format.enable": false,
"window.zoomLevel": -1,
"editor.tabSize": 2,
"typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis": true,
"editor.formatOnSave": true
}
}
13 changes: 13 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.fixAll.tslint": true
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"eslint.workingDirectories": [{ "mode": "auto" }],
"javascript.format.enable": false,
"window.zoomLevel": -1,
"editor.tabSize": 2,
"typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis": true,
"editor.formatOnSave": true
}
13 changes: 13 additions & 0 deletions smart_contract/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Subspace - Gemini
RPC_URL_GEMINI="https://domain-3.evm.gemini-3f.subspace.network/ws"
PRIVATE_KEY_GEMINI=""

# Goerli
RPC_URL_GOERLI="https://goerli.infura.io/v3/"
PRIVATE_KEY_GOERLI=""

# Blockscout - Gemini
GEMINI_SCAN_API_KEY=""

# Etherscan
ETHERSCAN_API_KEY=""
20 changes: 20 additions & 0 deletions smart_contract/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
node_modules
.env
coverage
coverage.json
typechain
typechain-types

# Hardhat files
cache
artifacts

Flat*.sol
deployments

# Foundry/lcov coverage report
report
lcov.info

# Wok in progress
.wip
1 change: 1 addition & 0 deletions smart_contract/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
8 changes: 8 additions & 0 deletions smart_contract/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"bracketSpacing": true,
"printWidth": 120,
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"useTabs": false
}
3 changes: 3 additions & 0 deletions smart_contract/.solcover.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
skipFiles: ['mocks/', 'test/'],
}
43 changes: 42 additions & 1 deletion smart_contract/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,42 @@
Project to facilitate work on Subspace Faucet, including discord bot, HTTP API server, and Smart Contact.
# Faucet Smart Contract for Subspace Network

## General info

Project to facilitate work on Subspace Faucet, including discord bot, HTTP API server, and Smart Contact.

## Functionality

- Request tokens from faucet

## Setup smart contract project

To run, test and/or deploy the smart contracts, first install the dependencies

### Install Dependencies

```bash
npm install
```

### Run test with Hardhat

```bash
npx hardhat test
```

### List all Hardhat tasks

```bash
npx hardhat help
```

### Run test with Foundry

```bash
forge test
```

## Documentation

- [Hardhat Documentation](https://hardhat.org/getting-started/)
- [Foundry Documentation](https://book.getfoundry.sh/index.html)
11 changes: 11 additions & 0 deletions smart_contract/constants/errors.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"MinterRole": {
"NotAdmin": "MinterRole_FunctionRestrictedToAdmin()",
"NotMinter": "MinterRole_FunctionRestrictedToMinter()"
},
"Faucet": {
"FailSendingNativeToken": "Faucet_SendingTokensFailed()",
"NoNativeTokenLeft": "Faucet_ContractBalanceIsZero()",
"InsufficientTimeElapsed": "Faucet_InsufficientTimeElapsed()"
}
}
5 changes: 5 additions & 0 deletions smart_contract/constants/values.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"Faucet": {
"CONTRACT_FILE_NAME": "Faucet"
}
}
81 changes: 43 additions & 38 deletions smart_contract/contracts/Faucet.sol
Original file line number Diff line number Diff line change
@@ -1,60 +1,65 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;
pragma solidity ^0.8.19;

contract Faucet {
address payable public owner = payable(msg.sender);
import './abstracts/MinterRole.sol';

error Faucet_InsufficientTimeElapsed();
error Faucet_SendingTokensFailed();
error Faucet_ContractBalanceIsZero();

contract Faucet is MinterRole {
uint256 public withdrawalAmount = 0.0006 * (10**18);
uint256 public lockTime = 1 minutes;

event Withdrawal(address indexed to, uint256 indexed amount);
event Deposit(address indexed from, uint256 indexed amount);

mapping(address => uint256) nextAccessTime;

function requestTokens(address payable recipient) public onlyOwner {
require(
msg.sender != address(0),
"Request must not originate from a zero account"
);
require(
address(this).balance >= withdrawalAmount,
"Insufficient balance in faucet for withdrawal request"
);
require(
block.timestamp >= nextAccessTime[recipient],
"Insufficient time elapsed since last withdrawal"
);
nextAccessTime[recipient] = block.timestamp + lockTime;
recipient.transfer(withdrawalAmount);
mapping(address => uint256) private _lastAccessTime;

constructor() {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}

receive() external payable {
emit Deposit(msg.sender, msg.value);
/// @notice Allow faucet owner to dispatch native tokens to the given address.
/// @param recipient The address to send tokens to.
function requestTokens(address recipient) public hasMinterRole {
if (nextAccessTime(recipient) > block.timestamp) revert Faucet_InsufficientTimeElapsed();

// If check pass set the next access time
_lastAccessTime[recipient] = block.timestamp;

// If check pass transfer the tokens
if (!payable(recipient).send(withdrawalAmount)) revert Faucet_SendingTokensFailed();
}

/// @notice Show the next access time for the given addres
/// @param recipient The address to send tokens to.
/// @return The next access time for the given address
function nextAccessTime(address recipient) public view returns (uint256) {
return _lastAccessTime[recipient] + lockTime;
}

function setWithdrawalAmount(uint256 amount) public onlyOwner {
withdrawalAmount = amount * (10**18);
/// @notice Allow faucet owner to change the withdrawal amount. (native and ERC20 tokens)
/// @param amount The amount to send.
function setWithdrawalAmount(uint256 amount) public hasAdminRole {
withdrawalAmount = amount;
}

function setLockTime(uint256 amount) public onlyOwner {
lockTime = amount * 1 minutes;
/// @notice Allow faucet owner to change the delay between withdrawals.
/// @param amount The amount of time to lock the faucet for. (for each receiver/type)
function setLockTime(uint256 amount) public hasAdminRole {
lockTime = amount;
}

function withdraw() external onlyOwner {
require(
address(this).balance > 0,
"No balance left in the contract"
);
owner.transfer(address(this).balance);
/// @notice Allow faucet owner to withdraw the native tokens from the contract.
function withdraw() external hasAdminRole {
if (address(this).balance == 0) revert Faucet_ContractBalanceIsZero();
address payable receiver = payable(_msgSender());
receiver.transfer(address(this).balance);
}

modifier onlyOwner() {
require(
msg.sender == owner,
"Only the contract owner can call this function"
);
_;
receive() external payable {
emit Deposit(msg.sender, msg.value);
}
}
63 changes: 63 additions & 0 deletions smart_contract/contracts/abstracts/MinterRole.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import '@openzeppelin/contracts/access/AccessControlEnumerable.sol';

error MinterRole_FunctionRestrictedToAdmin();
error MinterRole_FunctionRestrictedToMinter();

contract MinterRole is AccessControlEnumerable {
bytes32 public constant MINTER_ROLE = keccak256('MINTER_ROLE');

modifier hasAdminRole() {
if (!isAdmin(msg.sender)) revert MinterRole_FunctionRestrictedToAdmin();
_;
}

modifier hasMinterRole() {
if (!isMinter(msg.sender)) revert MinterRole_FunctionRestrictedToMinter();
_;
}

/// @notice Return if an address has admin role
/// @param admin The address to verify
/// @return True if the address has admin role
function isAdmin(address admin) public view returns (bool) {
return hasRole(DEFAULT_ADMIN_ROLE, admin);
}

/// @notice Give Admin Role to the given address.
/// @param admin The address to give the Admin Role.
/// @dev The call must originate from an admin.
function addAdmin(address admin) public hasAdminRole {
grantRole(DEFAULT_ADMIN_ROLE, admin);
}

/// @notice Revoke Admin Role from the given address.
/// @param admin The address to revoke the Admin Role.
/// @dev The call must originate from an admin.
function removeAdmin(address admin) public hasAdminRole {
revokeRole(DEFAULT_ADMIN_ROLE, admin);
}

/// @notice Return if an address has minter role
/// @param minter The address to verify
/// @return True if the address has minter role
function isMinter(address minter) public view returns (bool) {
return hasRole(MINTER_ROLE, minter);
}

/// @notice Give minter role to the given address.
/// @param minter The address to give the Minter Role.
/// @dev The call must originate from an admin.
function addMinter(address minter) public hasAdminRole {
grantRole(MINTER_ROLE, minter);
}

/// @notice Revoke minter role from the given address.
/// @param minter The address to revoke the Minter Role.
/// @dev The call must originate from an admin.
function removeMinter(address minter) public hasAdminRole {
revokeRole(MINTER_ROLE, minter);
}
}
Loading

0 comments on commit 551133b

Please sign in to comment.