first commit

This commit is contained in:
ButterflyEffect 2023-10-21 20:11:35 +00:00
commit 790a64e82a
15 changed files with 18600 additions and 0 deletions

11
.gitignore vendored Normal file

@ -0,0 +1,11 @@
node_modules
.env
coverage
coverage.json
typechain
typechain-types
# Hardhat files
cache
artifacts

19
.prettierrc Normal file

@ -0,0 +1,19 @@
{
"overrides": [
{
"files": "*.sol",
"options": {
"printWidth": 80,
"tabWidth": 4,
"useTabs": false,
"singleQuote": false,
"bracketSpacing": true
}
},
{"files": "*.js", "options": {
"bracketSpacing": true,
"printWidth": 120
}}
],
"bracketSpacing": true
}

13
README.md Normal file

@ -0,0 +1,13 @@
# Sample Hardhat Project
This project demonstrates a basic Hardhat use case. It comes with a sample contract, a test for that contract, and a script that deploys that contract.
Try running some of the following tasks:
```shell
npx hardhat help
npx hardhat test
REPORT_GAS=true npx hardhat test
npx hardhat node
npx hardhat run scripts/deploy.js
```

@ -0,0 +1,19 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import { IRelayerRegistryProxy } from "./interfaces/RelayerRegistryProxy.sol";
contract Proposal {
address immutable newRelayerRegistry;
constructor(address _newRelayerRegistry) public {
newRelayerRegistry = _newRelayerRegistry;
}
function executeProposal() public {
IRelayerRegistryProxy relayerRegistryProxy = IRelayerRegistryProxy(0x58E8dCC13BE9780fC42E8723D8EaD4CF46943dF2);
relayerRegistryProxy.upgradeTo(newRelayerRegistry);
}
}

@ -0,0 +1,370 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Initializable } from "@openzeppelin/contracts/proxy/Initializable.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { EnsResolve } from "torn-token/contracts/ENS.sol";
import { TORN } from "torn-token/contracts/TORN.sol";
import { IENS } from "./interfaces/ENS.sol";
import { IFeeManager } from "./interfaces/FeeManager.sol";
import { ITornadoInstance } from "./interfaces/TornadoInstance.sol";
import { ITornadoStakingRewards } from "./interfaces/TornadoStakingRewards.sol";
import { ENSNamehash } from "./libraries/EnsNamehash.sol";
struct RelayerState {
uint256 balance;
bytes32 ensHash;
}
/**
* @notice Registry contract, one of the main contracts of this protocol upgrade.
* The contract should store relayers' addresses and data attributed to the
* master address of the relayer. This data includes the relayers stake and
* his ensHash.
* A relayers master address has a number of subaddresses called "workers",
* these are all addresses which burn stake in communication with the proxy.
* If a relayer is not registered, he is not displayed on the frontend.
* @dev CONTRACT RISKS:
* - if setter functions are compromised, relayer metadata would be at risk, including the noted amount of his balance
* - if burn function is compromised, relayers run the risk of being unable to handle withdrawals
* - the above risk also applies to the nullify balance function
*
*/
contract RelayerRegistry is Initializable, EnsResolve {
using SafeMath for uint256;
using SafeERC20 for TORN;
using ENSNamehash for bytes;
TORN public immutable torn;
address public immutable governance;
IENS public immutable ens;
ITornadoStakingRewards public immutable staking;
IFeeManager public immutable feeManager;
address public tornadoRouter;
uint256 public minStakeAmount;
mapping(address => RelayerState) public relayers;
mapping(address => address) public workers;
event RelayerBalanceNullified(address relayer);
event WorkerRegistered(address relayer, address worker);
event WorkerUnregistered(address relayer, address worker);
event StakeAddedToRelayer(address relayer, uint256 amountStakeAdded);
event StakeBurned(address relayer, uint256 amountBurned);
event MinimumStakeAmount(uint256 minStakeAmount);
event RouterRegistered(address tornadoRouter);
event RelayerRegistered(
bytes32 relayer,
string ensName,
address relayerAddress,
uint256 stakedAmount
);
event RelayerUnregistered(address relayer);
modifier onlyGovernance() {
require(msg.sender == governance, "only governance");
_;
}
modifier onlyTornadoRouter() {
require(msg.sender == tornadoRouter, "only proxy");
_;
}
modifier onlyRelayer(address sender, address relayer) {
require(workers[sender] == relayer, "only relayer");
_;
}
constructor(
address _torn,
address _governance,
address _ens,
address _staking,
address _feeManager
) public {
torn = TORN(_torn);
governance = _governance;
ens = IENS(_ens);
staking = ITornadoStakingRewards(_staking);
feeManager = IFeeManager(_feeManager);
}
/**
* @notice initialize function for upgradeability
* @dev this contract will be deployed behind a proxy and should not assign values at logic address,
* params left out because self explainable
*
*/
function initialize(bytes32 _tornadoRouter) external initializer {
tornadoRouter = resolve(_tornadoRouter);
}
/**
* @notice This function should register a master address and optionally a set of workeres for a relayer + metadata
* @dev Relayer can't steal other relayers workers since they are registered, and a wallet (msg.sender check) can always unregister itself
* @param ensName ens name of the relayer
* @param stake the initial amount of stake in TORN the relayer is depositing
*
*/
function register(
string calldata ensName,
uint256 stake,
address[] calldata workersToRegister
) external {
_register(msg.sender, ensName, stake, workersToRegister);
}
/**
* @dev Register function equivalent with permit-approval instead of regular approve.
*
*/
function registerPermit(
string calldata ensName,
uint256 stake,
address[] calldata workersToRegister,
address relayer,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
torn.permit(relayer, address(this), stake, deadline, v, r, s);
_register(relayer, ensName, stake, workersToRegister);
}
function _register(
address relayer,
string calldata ensName,
uint256 stake,
address[] calldata workersToRegister
) internal {
bytes32 ensHash = bytes(ensName).namehash();
require(relayer == ens.owner(ensHash), "only ens owner");
require(workers[relayer] == address(0), "cant register again");
RelayerState storage metadata = relayers[relayer];
require(metadata.ensHash == bytes32(0), "registered already");
require(stake >= minStakeAmount, "!min_stake");
torn.safeTransferFrom(relayer, address(staking), stake);
emit StakeAddedToRelayer(relayer, stake);
metadata.balance = stake;
metadata.ensHash = ensHash;
workers[relayer] = relayer;
for (uint256 i = 0; i < workersToRegister.length; i++) {
address worker = workersToRegister[i];
_registerWorker(relayer, worker);
}
emit RelayerRegistered(ensHash, ensName, relayer, stake);
}
/**
* @notice This function should allow relayers to register more workeres
* @param relayer Relayer which should send message from any worker which is already registered
* @param worker Address to register
*
*/
function registerWorker(
address relayer,
address worker
) external onlyRelayer(msg.sender, relayer) {
_registerWorker(relayer, worker);
}
function _registerWorker(address relayer, address worker) internal {
require(workers[worker] == address(0), "can't steal an address");
workers[worker] = relayer;
emit WorkerRegistered(relayer, worker);
}
/**
* @notice This function should allow anybody to unregister an address they own
* @dev designed this way as to allow someone to unregister themselves in case a relayer misbehaves
* - this should be followed by an action like burning relayer stake
* - there was an option of allowing the sender to burn relayer stake in case of malicious behaviour, this feature was not included in the end
* - reverts if trying to unregister master, otherwise contract would break. in general, there should be no reason to unregister master at all
*
*/
function unregisterWorker(address worker) external {
if (worker != msg.sender)
require(workers[worker] == msg.sender, "only owner of worker");
require(workers[worker] != worker, "cant unregister master");
emit WorkerUnregistered(workers[worker], worker);
workers[worker] = address(0);
}
/**
* @notice This function should allow governance to unregister relayer
* @param relayer Address of the relayer
*
*/
function unregisterRelayer(address relayer) external onlyGovernance {
nullifyBalance(relayer);
delete relayers[relayer];
delete workers[relayer];
emit RelayerUnregistered(relayer);
}
/**
* @notice This function should allow anybody to stake to a relayer more TORN
* @param relayer Relayer main address to stake to
* @param stake Stake to be added to relayer
*
*/
function stakeToRelayer(address relayer, uint256 stake) external {
_stakeToRelayer(msg.sender, relayer, stake);
}
/**
* @dev stakeToRelayer function equivalent with permit-approval instead of regular approve.
* @param staker address from that stake is paid
*
*/
function stakeToRelayerPermit(
address relayer,
uint256 stake,
address staker,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
torn.permit(staker, address(this), stake, deadline, v, r, s);
_stakeToRelayer(staker, relayer, stake);
}
function _stakeToRelayer(
address staker,
address relayer,
uint256 stake
) internal {
require(workers[relayer] == relayer, "!registered");
torn.safeTransferFrom(staker, address(staking), stake);
relayers[relayer].balance = stake.add(relayers[relayer].balance);
emit StakeAddedToRelayer(relayer, stake);
}
/**
* @notice This function should burn some relayer stake on withdraw and notify staking of this
* @dev IMPORTANT FUNCTION:
* - This should be only called by the tornado proxy
* - Should revert if relayer does not call proxy from valid worker
* - Should not overflow
* - Should underflow and revert (SafeMath) on not enough stake (balance)
* @param sender worker to check sender == relayer
* @param relayer address of relayer who's stake is being burned
* @param pool instance to get fee for
*
*/
function burn(
address sender,
address relayer,
ITornadoInstance pool
) external onlyTornadoRouter {
address masterAddress = workers[sender];
if (masterAddress == address(0)) {
require(workers[relayer] == address(0), "Only custom relayer");
return;
}
require(masterAddress == relayer, "only relayer");
uint256 toBurn = feeManager.instanceFeeWithUpdate(pool);
relayers[relayer].balance = relayers[relayer].balance.sub(toBurn);
staking.addBurnRewards(toBurn);
emit StakeBurned(relayer, toBurn);
}
/**
* @notice This function should allow governance to set the minimum stake amount
* @param minAmount new minimum stake amount
*
*/
function setMinStakeAmount(uint256 minAmount) external onlyGovernance {
minStakeAmount = minAmount;
emit MinimumStakeAmount(minAmount);
}
/**
* @notice This function should allow governance to set a new tornado proxy address
* @param tornadoRouterAddress address of the new proxy
*
*/
function setTornadoRouter(
address tornadoRouterAddress
) external onlyGovernance {
tornadoRouter = tornadoRouterAddress;
emit RouterRegistered(tornadoRouterAddress);
}
/**
* @notice This function should allow governance to nullify a relayers balance
* @dev IMPORTANT FUNCTION:
* - Should nullify the balance
* - Adding nullified balance as rewards was refactored to allow for the flexibility of these funds (for gov to operate with them)
* @param relayer address of relayer who's balance is to nullify
*
*/
function nullifyBalance(address relayer) public onlyGovernance {
address masterAddress = workers[relayer];
require(relayer == masterAddress, "must be master");
relayers[masterAddress].balance = 0;
emit RelayerBalanceNullified(relayer);
}
/**
* @notice This function should check if a worker is associated with a relayer
* @param toResolve address to check
* @return true if is associated
*
*/
function isRelayer(address toResolve) external view returns (bool) {
return workers[toResolve] != address(0);
}
/**
* @notice This function should check if a worker is registered to the relayer stated
* @param relayer relayer to check
* @param toResolve address to check
* @return true if registered
*
*/
function isRelayerRegistered(
address relayer,
address toResolve
) external view returns (bool) {
return workers[toResolve] == relayer;
}
/**
* @notice This function should get a relayers ensHash
* @param relayer address to fetch for
* @return relayer's ensHash
*
*/
function getRelayerEnsHash(
address relayer
) external view returns (bytes32) {
return relayers[workers[relayer]].ensHash;
}
/**
* @notice This function should get a relayers balance
* @param relayer relayer who's balance is to fetch
* @return relayer's balance
*
*/
function getRelayerBalance(
address relayer
) external view returns (uint256) {
return relayers[workers[relayer]].balance;
}
}

@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
interface IENS {
function owner(bytes32 node) external view returns (address);
}

@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
import { ITornadoInstance } from "./TornadoInstance.sol";
interface IFeeManager {
function instanceFeeWithUpdate(
ITornadoInstance _instance
) external returns (uint160);
}

@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
interface IRelayerRegistryProxy {
function upgradeTo(address newImplementation) external;
}

@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
interface ITornadoInstance {
function token() external view returns (address);
function denomination() external view returns (uint256);
function deposit(bytes32 commitment) external payable;
function withdraw(
bytes calldata proof,
bytes32 root,
bytes32 nullifierHash,
address payable recipient,
address payable relayer,
uint256 fee,
uint256 refund
) external payable;
}

@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
interface ITornadoStakingRewards {
function addBurnRewards(uint256 amount) external;
}

@ -0,0 +1,38 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
/*
* @dev Solidity implementation of the ENS namehash algorithm.
*
* Warning! Does not normalize or validate names before hashing.
* Original version can be found here https://github.com/JonahGroendal/ens-namehash/
*/
library ENSNamehash {
function namehash(bytes memory domain) internal pure returns (bytes32) {
return namehash(domain, 0);
}
function namehash(bytes memory domain, uint256 i) internal pure returns (bytes32) {
if (domain.length <= i) return 0x0000000000000000000000000000000000000000000000000000000000000000;
uint256 len = labelLength(domain, i);
return keccak256(abi.encodePacked(namehash(domain, i + len + 1), keccak(domain, i, len)));
}
function labelLength(bytes memory domain, uint256 i) private pure returns (uint256) {
uint256 len;
while (i + len != domain.length && domain[i + len] != 0x2e) {
len++;
}
return len;
}
function keccak(bytes memory data, uint256 offset, uint256 len) private pure returns (bytes32 ret) {
require(offset + len <= data.length);
assembly {
ret := keccak256(add(add(data, 32), offset), len)
}
}
}

26
hardhat.config.js Normal file

@ -0,0 +1,26 @@
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config()
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.6.12",
networks: {
mainnet: {
url: "https://eth.llamarpc.com"
},
testnet: {
url: "https://ethereum-goerli.publicnode.com",
accounts: [process.env.TEST_PK]
},
hardhat: {
forking: {
url: "https://eth.llamarpc.com",
enabled: true,
blockNumber: 18391425
}
}
},
etherscan: {
apiKey: process.env.ETHERSCAN_KEY
}
};

17956
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

41
package.json Normal file

@ -0,0 +1,41 @@
{
"name": "proposal-32",
"version": "1.0.0",
"description": "This project demonstrates a basic Hardhat use case. It comes with a sample contract, a test for that contract, and a script that deploys that contract.",
"main": "hardhat.config.js",
"directories": {
"test": "test"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@nomicfoundation/hardhat-chai-matchers": "^2.0.2",
"@nomicfoundation/hardhat-ethers": "^3.0.4",
"@nomicfoundation/hardhat-network-helpers": "^1.0.9",
"@nomicfoundation/hardhat-toolbox": "^3.0.0",
"@nomicfoundation/hardhat-verify": "^1.1.1",
"@typechain/ethers-v6": "^0.4.3",
"@typechain/hardhat": "^8.0.3",
"@types/chai": "^4.3.9",
"@types/mocha": "^10.0.2",
"chai": "^4.3.10",
"dotenv": "^16.3.1",
"ethers": "^6.8.0",
"hardhat": "^2.18.1",
"hardhat-gas-reporter": "^1.0.9",
"prettier": "^3.0.3",
"prettier-plugin-solidity": "^1.1.3",
"solidity-coverage": "^0.8.5",
"ts-node": "^10.9.1",
"typechain": "^8.3.2",
"typescript": "^5.2.2"
},
"dependencies": {
"@openzeppelin/contracts": "^3.2.0-rc.0",
"@openzeppelin/upgrades-core": "^1.30.1",
"torn-token": "^1.0.8"
}
}

53
scripts/deploy.js Normal file

@ -0,0 +1,53 @@
// We require the Hardhat Runtime Environment explicitly here. This is optional
// but useful for running the script in a standalone fashion through `node <script>`.
//
// You can also run a script with `npx hardhat run <script>`. If you do that, Hardhat
// will compile your contracts, add the Hardhat Runtime Environment's members to the
// global scope, and execute the script.
const hre = require("hardhat");
const { ethers } = require("hardhat");
async function main() {
const RelayerRegistryFactory = await ethers.getContractFactory("RelayerRegistry");
const constructorArgs = ["0x77777FeDdddFfC19Ff86DB637967013e6C6A116C", "0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce", "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", "0x5B3f656C80E8ddb9ec01Dd9018815576E9238c29", "0x5f6c97C6AD7bdd0AE7E0Dd4ca33A4ED3fDabD4D7"];
const relayerRegistry = await RelayerRegistryFactory.deploy(...constructorArgs);
const deployedRegistryAddr = await relayerRegistry.getAddress();
console.log(`Relayer registry contract deployed by address ${deployedRegistryAddr}, waiting for blockchain confirmations...`)
let tx = relayerRegistry.deploymentTransaction();
await tx.wait(6);
console.log("Deployment confirmed with 6 blocks, waiting for verification on Etherscan");
await hre.run("verify:verify",
{
address: deployedRegistryAddr,
contract: "contracts/RelayerRegistry.sol:RelayerRegistry",
constructorArguments: constructorArgs
});
console.log("Relayer registry contract deployed and verified!\n\n");
const proposalFactory = await ethers.getContractFactory("Proposal");
const proposal = await proposalFactory.deploy(deployedRegistryAddr);
const deployedProposalAddr = await proposal.getAddress();
console.log(`Proposal contract deployed by address ${deployedProposalAddr}, waiting for blockchain confirmations...`);
tx = proposal.deploymentTransaction();
await tx.wait(6);
console.log("Deployment confirmed with 6 blocks, waiting for verification on Etherscan");
await hre.run("verify:verify",
{
address: deployedProposalAddr,
contract: "contracts/RegistryUpgradeProposal.sol:Proposal",
constructorArguments: [deployedRegistryAddr]
});
}
// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});