code and tests

This commit is contained in:
ButterflyEffect 2024-01-31 10:12:30 +00:00
commit 3d871f2f2f
38 changed files with 21708 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": 120,
"tabWidth": 4,
"useTabs": false,
"singleQuote": false,
"bracketSpacing": true
}
},
{"files": "*.js", "options": {
"bracketSpacing": true,
"printWidth": 120
}}
],
"bracketSpacing": true
}

9
README.md Normal file

@ -0,0 +1,9 @@
# Proposal 46
Fix proposals state and update docs
Deploy: npx hardhat run --network mainnet script/deploy.js
Tests: npm run test
Dont forget to fill env file

34
contracts/Proposal.sol Normal file

@ -0,0 +1,34 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
import "./interfaces/ILoopbackProxy.sol";
import "./interfaces/IENSResolver.sol";
import "./libraries/Base58.sol";
import "./libraries/EnsNamehash.sol";
contract Proposal {
using ENSNamehash for bytes;
using Base58 for bytes;
IENSResolver constant ensResolver = IENSResolver(0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41);
string constant docsEns = "docs.tornadocash.eth";
string constant docsSourceEns = "docs.sources.tornadocash.eth";
string constant docsIpfs = "QmYjBe2bNTGQzgdqAi7hkdNJ9BBrAgf4e8teRWLa4oN6Y6";
string constant docsSourceIpfs = "QmcdTqAMD4nvDhAfWkEnKCQHhFk8K7XmFDouextcfrjWif";
address constant governanceAddr = 0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce;
address immutable newGovernanceImplementation;
constructor(address _newGovernanceImplementation) {
newGovernanceImplementation = _newGovernanceImplementation;
}
function calculateIpfsContenthash(string memory ipfsCid) internal pure returns (bytes memory) {
return bytes.concat(hex"e3010170", Base58.decodeFromString(ipfsCid));
}
function executeProposal() external {
ensResolver.setContenthash(bytes(docsEns).namehash(), calculateIpfsContenthash(docsIpfs));
ensResolver.setContenthash(bytes(docsSourceEns).namehash(), calculateIpfsContenthash(docsSourceIpfs));
ILoopbackProxy(governanceAddr).upgradeTo(newGovernanceImplementation);
}
}

@ -0,0 +1,13 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
interface IENSResolver {
function setContenthash(bytes32 node, bytes memory hash) external;
function setAddr(bytes32 node, address a) external;
function addr(bytes32 node) external view returns (address);
function contenthash(bytes32 node) external returns (bytes memory);
}

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

@ -0,0 +1,109 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
/**
* @title Base58
* @author storyicon@foxmail.com
* @notice This algorithm was migrated from github.com/mr-tron/base58 to solidity.
* Note that it is not yet optimized for gas, so it is recommended to use it only in the view/pure function.
*/
library Base58 {
bytes constant ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
/**
* @notice decode is used to decode the given string in base58 standard.
* @param data_ data encoded with base58, passed in as bytes.
* @return raw data, returned as bytes.
*/
function decode(bytes memory data_) internal pure returns (bytes memory) {
unchecked {
uint256 zero = 49;
uint256 b58sz = data_.length;
uint256 zcount = 0;
for (uint256 i = 0; i < b58sz && uint8(data_[i]) == zero; i++) {
zcount++;
}
uint256 t;
uint256 c;
bool f;
bytes memory binu = new bytes(2 * (((b58sz * 8351) / 6115) + 1));
uint32[] memory outi = new uint32[]((b58sz + 3) / 4);
for (uint256 i = 0; i < data_.length; i++) {
bytes1 r = data_[i];
(c, f) = indexOf(ALPHABET, r);
require(f, "invalid base58 digit");
for (int256 k = int256(outi.length) - 1; k >= 0; k--) {
t = uint64(outi[uint256(k)]) * 58 + c;
c = t >> 32;
outi[uint256(k)] = uint32(t & 0xffffffff);
}
}
uint64 mask = uint64(b58sz % 4) * 8;
if (mask == 0) {
mask = 32;
}
mask -= 8;
uint256 outLen = 0;
for (uint256 j = 0; j < outi.length; j++) {
while (mask < 32) {
binu[outLen] = bytes1(uint8(outi[j] >> mask));
outLen++;
if (mask < 8) {
break;
}
mask -= 8;
}
mask = 24;
}
for (uint256 msb = zcount; msb < binu.length; msb++) {
if (binu[msb] > 0) {
return slice(binu, msb - zcount, outLen);
}
}
return slice(binu, 0, outLen);
}
}
/**
* @notice decode is used to decode the given string in base58 standard.
* @param data_ data encoded with base58, passed in as string.
* @return raw data, returned as bytes.
*/
function decodeFromString(string memory data_) internal pure returns (bytes memory) {
return decode(bytes(data_));
}
/**
* @notice slice is used to slice the given byte, returns the bytes in the range of [start_, end_)
* @param data_ raw data, passed in as bytes.
* @param start_ start index.
* @param end_ end index.
* @return slice data
*/
function slice(bytes memory data_, uint256 start_, uint256 end_) private pure returns (bytes memory) {
unchecked {
bytes memory ret = new bytes(end_ - start_);
for (uint256 i = 0; i < end_ - start_; i++) {
ret[i] = data_[i + start_];
}
return ret;
}
}
/**
* @notice indexOf is used to find where char_ appears in data_.
* @param data_ raw data, passed in as bytes.
* @param char_ target byte.
* @return index, and whether the search was successful.
*/
function indexOf(bytes memory data_, bytes1 char_) public pure returns (uint256, bool) {
unchecked {
for (uint256 i = 0; i < data_.length; i++) {
if (data_[i] == char_) {
return (i, true);
}
}
return (0, false);
}
}
}

@ -0,0 +1,38 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
/*
* @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)
}
}
}

@ -0,0 +1,73 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Configuration {
/// @notice Time delay between proposal vote completion and its execution
uint256 public EXECUTION_DELAY;
/// @notice Time before a passed proposal is considered expired
uint256 public EXECUTION_EXPIRATION;
/// @notice The number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed
uint256 public QUORUM_VOTES;
/// @notice The number of votes required in order for a voter to become a proposer
uint256 public PROPOSAL_THRESHOLD;
/// @notice The delay before voting on a proposal may take place, once proposed
/// It is needed to prevent reorg attacks that replace the proposal
uint256 public VOTING_DELAY;
/// @notice The duration of voting on a proposal
uint256 public VOTING_PERIOD;
/// @notice If the outcome of a proposal changes during CLOSING_PERIOD, the vote will be extended by VOTE_EXTEND_TIME (no more than once)
uint256 public CLOSING_PERIOD;
/// @notice If the outcome of a proposal changes during CLOSING_PERIOD, the vote will be extended by VOTE_EXTEND_TIME (no more than once)
uint256 public VOTE_EXTEND_TIME;
modifier onlySelf() {
require(msg.sender == address(this), "Governance: unauthorized");
_;
}
function _initializeConfiguration() internal {
EXECUTION_DELAY = 2 days;
EXECUTION_EXPIRATION = 3 days;
QUORUM_VOTES = 25_000e18; // 0.25% of TORN
PROPOSAL_THRESHOLD = 1000e18; // 0.01% of TORN
VOTING_DELAY = 75 seconds;
VOTING_PERIOD = 3 days;
CLOSING_PERIOD = 1 hours;
VOTE_EXTEND_TIME = 6 hours;
}
function setExecutionDelay(uint256 executionDelay) external onlySelf {
EXECUTION_DELAY = executionDelay;
}
function setExecutionExpiration(uint256 executionExpiration) external onlySelf {
EXECUTION_EXPIRATION = executionExpiration;
}
function setQuorumVotes(uint256 quorumVotes) external onlySelf {
QUORUM_VOTES = quorumVotes;
}
function setProposalThreshold(uint256 proposalThreshold) external onlySelf {
PROPOSAL_THRESHOLD = proposalThreshold;
}
function setVotingDelay(uint256 votingDelay) external onlySelf {
VOTING_DELAY = votingDelay;
}
function setVotingPeriod(uint256 votingPeriod) external onlySelf {
VOTING_PERIOD = votingPeriod;
}
function setClosingPeriod(uint256 closingPeriod) external onlySelf {
CLOSING_PERIOD = closingPeriod;
}
function setVoteExtendTime(uint256 voteExtendTime) external onlySelf {
// VOTE_EXTEND_TIME should be less EXECUTION_DELAY to prevent double voting
require(voteExtendTime < EXECUTION_DELAY, "Governance: incorrect voteExtendTime");
VOTE_EXTEND_TIME = voteExtendTime;
}
}

9
contracts/v1/Core.sol Normal file

@ -0,0 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
abstract contract Core {
/// @notice Locked token balance for each account
mapping(address => uint256) public lockedBalance;
}

@ -0,0 +1,59 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import "./Core.sol";
abstract contract Delegation is Core {
/// @notice Delegatee records
mapping(address => address) public delegatedTo;
event Delegated(address indexed account, address indexed to);
event Undelegated(address indexed account, address indexed from);
function delegate(address to) external {
address previous = delegatedTo[msg.sender];
require(
to != msg.sender && to != address(this) && to != address(0) && to != previous,
"Governance: invalid delegatee"
);
if (previous != address(0)) {
emit Undelegated(msg.sender, previous);
}
delegatedTo[msg.sender] = to;
emit Delegated(msg.sender, to);
}
function undelegate() external {
address previous = delegatedTo[msg.sender];
require(previous != address(0), "Governance: tokens are already undelegated");
delegatedTo[msg.sender] = address(0);
emit Undelegated(msg.sender, previous);
}
function proposeByDelegate(address from, address target, string memory description)
external
returns (uint256)
{
require(delegatedTo[from] == msg.sender, "Governance: not authorized");
return _propose(from, target, description);
}
function _propose(address proposer, address target, string memory description)
internal
virtual
returns (uint256);
function castDelegatedVote(address[] memory from, uint256 proposalId, bool support) external virtual {
for (uint256 i = 0; i < from.length; i++) {
require(delegatedTo[from[i]] == msg.sender, "Governance: not authorized");
_castVote(from[i], proposalId, support);
}
if (lockedBalance[msg.sender] > 0) {
_castVote(msg.sender, proposalId, support);
}
}
function _castVote(address voter, uint256 proposalId, bool support) internal virtual;
}

283
contracts/v1/Governance.sol Normal file

@ -0,0 +1,283 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/upgrades-core/contracts/Initializable.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "torn-token/contracts/ENS.sol";
import "torn-token/contracts/TORN.sol";
import "./Delegation.sol";
import "./Configuration.sol";
contract Governance is Initializable, Configuration, Delegation, EnsResolve {
using SafeMath for uint256;
/// @notice Possible states that a proposal may be in
enum ProposalState {
Pending,
Active,
Defeated,
Timelocked,
AwaitingExecution,
Executed,
Expired
}
struct Proposal {
// Creator of the proposal
address proposer;
// target addresses for the call to be made
address target;
// The block at which voting begins
uint256 startTime;
// The block at which voting ends: votes must be cast prior to this block
uint256 endTime;
// Current number of votes in favor of this proposal
uint256 forVotes;
// Current number of votes in opposition to this proposal
uint256 againstVotes;
// Flag marking whether the proposal has been executed
bool executed;
// Flag marking whether the proposal voting time has been extended
// Voting time can be extended once, if the proposal outcome has changed during CLOSING_PERIOD
bool extended;
// Receipts of ballots for the entire set of voters
mapping(address => Receipt) receipts;
}
/// @notice Ballot receipt record for a voter
struct Receipt {
// Whether or not a vote has been cast
bool hasVoted;
// Whether or not the voter supports the proposal
bool support;
// The number of votes the voter had, which were cast
uint256 votes;
}
/// @notice The official record of all proposals ever proposed
Proposal[] public proposals;
/// @notice The latest proposal for each proposer
mapping(address => uint256) public latestProposalIds;
/// @notice Timestamp when a user can withdraw tokens
mapping(address => uint256) public canWithdrawAfter;
TORN public torn;
/// @notice An event emitted when a new proposal is created
event ProposalCreated(
uint256 indexed id,
address indexed proposer,
address target,
uint256 startTime,
uint256 endTime,
string description
);
/// @notice An event emitted when a vote has been cast on a proposal
event Voted(uint256 indexed proposalId, address indexed voter, bool indexed support, uint256 votes);
/// @notice An event emitted when a proposal has been executed
event ProposalExecuted(uint256 indexed proposalId);
/// @notice Makes this instance inoperable to prevent selfdestruct attack
/// Proxy will still be able to properly initialize its storage
constructor() public initializer {
torn = TORN(0x000000000000000000000000000000000000dEaD);
_initializeConfiguration();
}
function initialize(bytes32 _torn) public initializer {
torn = TORN(resolve(_torn));
// Create a dummy proposal so that indexes start from 1
proposals.push(
Proposal({
proposer: address(this),
target: 0x000000000000000000000000000000000000dEaD,
startTime: 0,
endTime: 0,
forVotes: 0,
againstVotes: 0,
executed: true,
extended: false
})
);
_initializeConfiguration();
}
function lock(address owner, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public virtual {
torn.permit(owner, address(this), amount, deadline, v, r, s);
_transferTokens(owner, amount);
}
function lockWithApproval(uint256 amount) public virtual {
_transferTokens(msg.sender, amount);
}
function unlock(uint256 amount) public virtual {
require(getBlockTimestamp() > canWithdrawAfter[msg.sender], "Governance: tokens are locked");
lockedBalance[msg.sender] = lockedBalance[msg.sender].sub(amount, "Governance: insufficient balance");
require(torn.transfer(msg.sender, amount), "TORN: transfer failed");
}
function propose(address target, string memory description) external returns (uint256) {
return _propose(msg.sender, target, description);
}
/**
* @notice Propose implementation
* @param proposer proposer address
* @param target smart contact address that will be executed as result of voting
* @param description description of the proposal
* @return the new proposal id
*/
function _propose(
address proposer,
address target,
string memory description
) internal virtual override(Delegation) returns (uint256) {
uint256 votingPower = lockedBalance[proposer];
require(votingPower >= PROPOSAL_THRESHOLD, "Governance::propose: proposer votes below proposal threshold");
// target should be a contract
require(Address.isContract(target), "Governance::propose: not a contract");
uint256 latestProposalId = latestProposalIds[proposer];
if (latestProposalId != 0) {
ProposalState proposersLatestProposalState = state(latestProposalId);
require(
proposersLatestProposalState != ProposalState.Active &&
proposersLatestProposalState != ProposalState.Pending,
"Governance::propose: one live proposal per proposer, found an already active proposal"
);
}
uint256 startTime = getBlockTimestamp().add(VOTING_DELAY);
uint256 endTime = startTime.add(VOTING_PERIOD);
Proposal memory newProposal = Proposal({
proposer: proposer,
target: target,
startTime: startTime,
endTime: endTime,
forVotes: 0,
againstVotes: 0,
executed: false,
extended: false
});
proposals.push(newProposal);
uint256 proposalId = proposalCount();
latestProposalIds[newProposal.proposer] = proposalId;
_lockTokens(proposer, endTime.add(VOTE_EXTEND_TIME).add(EXECUTION_EXPIRATION).add(EXECUTION_DELAY));
emit ProposalCreated(proposalId, proposer, target, startTime, endTime, description);
return proposalId;
}
function execute(uint256 proposalId) public payable virtual {
require(state(proposalId) == ProposalState.AwaitingExecution, "Governance::execute: invalid proposal state");
Proposal storage proposal = proposals[proposalId];
proposal.executed = true;
address target = proposal.target;
require(Address.isContract(target), "Governance::execute: not a contract");
(bool success, bytes memory data) = target.delegatecall(abi.encodeWithSignature("executeProposal()"));
if (!success) {
if (data.length > 0) {
revert(string(data));
} else {
revert("Proposal execution failed");
}
}
emit ProposalExecuted(proposalId);
}
function castVote(uint256 proposalId, bool support) external virtual {
_castVote(msg.sender, proposalId, support);
}
function _castVote(address voter, uint256 proposalId, bool support) internal override(Delegation) {
require(state(proposalId) == ProposalState.Active, "Governance::_castVote: voting is closed");
Proposal storage proposal = proposals[proposalId];
Receipt storage receipt = proposal.receipts[voter];
bool beforeVotingState = proposal.forVotes <= proposal.againstVotes;
uint256 votes = lockedBalance[voter];
require(votes > 0, "Governance: balance is 0");
if (receipt.hasVoted) {
if (receipt.support) {
proposal.forVotes = proposal.forVotes.sub(receipt.votes);
} else {
proposal.againstVotes = proposal.againstVotes.sub(receipt.votes);
}
}
if (support) {
proposal.forVotes = proposal.forVotes.add(votes);
} else {
proposal.againstVotes = proposal.againstVotes.add(votes);
}
if (!proposal.extended && proposal.endTime.sub(getBlockTimestamp()) < CLOSING_PERIOD) {
bool afterVotingState = proposal.forVotes <= proposal.againstVotes;
if (beforeVotingState != afterVotingState) {
proposal.extended = true;
proposal.endTime = proposal.endTime.add(VOTE_EXTEND_TIME);
}
}
receipt.hasVoted = true;
receipt.support = support;
receipt.votes = votes;
_lockTokens(voter, proposal.endTime.add(VOTE_EXTEND_TIME).add(EXECUTION_EXPIRATION).add(EXECUTION_DELAY));
emit Voted(proposalId, voter, support, votes);
}
function _lockTokens(address owner, uint256 timestamp) internal {
if (timestamp > canWithdrawAfter[owner]) {
canWithdrawAfter[owner] = timestamp;
}
}
function _transferTokens(address owner, uint256 amount) internal virtual {
require(torn.transferFrom(owner, address(this), amount), "TORN: transferFrom failed");
lockedBalance[owner] = lockedBalance[owner].add(amount);
}
function getReceipt(uint256 proposalId, address voter) public view returns (Receipt memory) {
return proposals[proposalId].receipts[voter];
}
function state(uint256 proposalId) public view virtual returns (ProposalState) {
require(proposalId <= proposalCount() && proposalId > 0, "Governance::state: invalid proposal id");
Proposal storage proposal = proposals[proposalId];
if (getBlockTimestamp() <= proposal.startTime) {
return ProposalState.Pending;
} else if (getBlockTimestamp() <= proposal.endTime) {
return ProposalState.Active;
} else if (
proposal.forVotes <= proposal.againstVotes || proposal.forVotes + proposal.againstVotes < QUORUM_VOTES
) {
return ProposalState.Defeated;
} else if (proposal.executed) {
return ProposalState.Executed;
} else if (getBlockTimestamp() >= proposal.endTime.add(EXECUTION_DELAY).add(EXECUTION_EXPIRATION)) {
return ProposalState.Expired;
} else if (getBlockTimestamp() >= proposal.endTime.add(EXECUTION_DELAY)) {
return ProposalState.AwaitingExecution;
} else {
return ProposalState.Timelocked;
}
}
function proposalCount() public view returns (uint256) {
return proposals.length - 1;
}
function getBlockTimestamp() internal view virtual returns (uint256) {
// solium-disable-next-line security/no-block-members
return block.timestamp;
}
}

@ -0,0 +1,26 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import "@openzeppelin/contracts/proxy/TransparentUpgradeableProxy.sol";
import "torn-token/contracts/ENS.sol";
/**
* @dev TransparentUpgradeableProxy that sets its admin to the implementation itself.
* It is also allowed to call implementation methods.
*/
contract LoopbackProxy is TransparentUpgradeableProxy, EnsResolve {
/**
* @dev Initializes an upgradeable proxy backed by the implementation at `_logic`.
*/
constructor(address _logic, bytes memory _data)
public
payable
TransparentUpgradeableProxy(_logic, address(this), _data)
{ }
/**
* @dev Override to allow admin (itself) access the fallback function.
*/
function _beforeFallback() internal override { }
}

@ -0,0 +1,58 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
interface IGasCompensationVault {
function compensateGas(address recipient, uint256 gasAmount) external;
function withdrawToGovernance(uint256 amount) external;
}
/**
* @notice This abstract contract is used to add gas compensation functionality to a contract.
*
*/
abstract contract GasCompensator {
using SafeMath for uint256;
/// @notice this vault is necessary for the gas compensation functionality to work
IGasCompensationVault public immutable gasCompensationVault;
constructor(address _gasCompensationVault) public {
gasCompensationVault = IGasCompensationVault(_gasCompensationVault);
}
/**
* @notice modifier which should compensate gas to account if eligible
* @dev Consider reentrancy, repeated calling of the function being compensated, eligibility.
* @param account address to be compensated
* @param eligible if the account is eligible for compensations or not
* @param extra extra amount in gas to be compensated, will be multiplied by basefee
*
*/
modifier gasCompensation(address account, bool eligible, uint256 extra) {
if (eligible) {
uint256 startGas = gasleft();
_;
uint256 gasToCompensate = startGas.sub(gasleft()).add(extra).add(10e3);
gasCompensationVault.compensateGas(account, gasToCompensate);
} else {
_;
}
}
/**
* @notice inheritable unimplemented function to withdraw ether from the vault
*
*/
function withdrawFromHelper(uint256 amount) external virtual;
/**
* @notice inheritable unimplemented function to deposit ether into the vault
*
*/
function setGasCompensations(uint256 _gasCompensationsLimit) external virtual;
}

@ -0,0 +1,175 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import { GovernanceVaultUpgrade } from "./GovernanceVaultUpgrade.sol";
import { GasCompensator } from "./GasCompensator.sol";
import { Math } from "@openzeppelin/contracts/math/Math.sol";
/**
* @notice This contract should upgrade governance to be able to compensate gas for certain actions.
* These actions are set to castVote, castDelegatedVote in this contract.
*
*/
contract GovernanceGasUpgrade is GovernanceVaultUpgrade, GasCompensator {
/**
* @notice constructor
* @param _gasCompLogic gas compensation vault address
* @param _userVault tornado vault address
*
*/
constructor(address _gasCompLogic, address _userVault)
public
GovernanceVaultUpgrade(_userVault)
GasCompensator(_gasCompLogic)
{ }
/// @notice check that msg.sender is multisig
modifier onlyMultisig() {
require(msg.sender == returnMultisigAddress(), "only multisig");
_;
}
/**
* @notice receive ether function, does nothing but receive ether
*
*/
receive() external payable { }
/**
* @notice function to add a certain amount of ether for gas compensations
* @dev send ether is used in the logic as we don't expect multisig to make a reentrancy attack on governance
* @param gasCompensationsLimit the amount of gas to be compensated
*
*/
function setGasCompensations(uint256 gasCompensationsLimit) external virtual override onlyMultisig {
require(
payable(address(gasCompensationVault)).send(
Math.min(gasCompensationsLimit, address(this).balance)
)
);
}
/**
* @notice function to withdraw funds from the gas compensator
* @dev send ether is used in the logic as we don't expect multisig to make a reentrancy attack on governance
* @param amount the amount of ether to withdraw
*
*/
function withdrawFromHelper(uint256 amount) external virtual override onlyMultisig {
gasCompensationVault.withdrawToGovernance(amount);
}
/**
* @notice function to cast callers votes on a proposal
* @dev IMPORTANT: This function uses the gasCompensation modifier.
* as such this function can trigger a payable fallback.
* It is not possible to vote without revert more than once,
* without hasAccountVoted being true, eliminating gas refunds in this case.
* Gas compensation is also using the low level send(), forwarding 23000 gas
* as to disallow further logic execution above that threshold.
* @param proposalId id of proposal account is voting on
* @param support true if yes false if no
*
*/
function castVote(uint256 proposalId, bool support)
external
virtual
override
gasCompensation(
msg.sender,
!hasAccountVoted(proposalId, msg.sender) && !checkIfQuorumReached(proposalId),
(msg.sender == tx.origin ? 21e3 : 0)
)
{
_castVote(msg.sender, proposalId, support);
}
/**
* @notice function to cast callers votes and votes delegated to the caller
* @param from array of addresses that should have delegated to voter
* @param proposalId id of proposal account is voting on
* @param support true if yes false if no
*
*/
function castDelegatedVote(address[] memory from, uint256 proposalId, bool support)
external
virtual
override
{
require(from.length > 0, "Can not be empty");
_castDelegatedVote(
from,
proposalId,
support,
!hasAccountVoted(proposalId, msg.sender) && !checkIfQuorumReached(proposalId)
);
}
/// @notice checker for success on deployment
/// @return returns precise version of governance
function version() external pure virtual override returns (string memory) {
return "2.lottery-and-gas-upgrade";
}
/**
* @notice function to check if quorum has been reached on a given proposal
* @param proposalId id of proposal
* @return true if quorum has been reached
*
*/
function checkIfQuorumReached(uint256 proposalId) public view returns (bool) {
return (proposals[proposalId].forVotes + proposals[proposalId].againstVotes >= QUORUM_VOTES);
}
/**
* @notice function to check if account has voted on a proposal
* @param proposalId id of proposal account should have voted on
* @param account address of the account
* @return true if acc has voted
*
*/
function hasAccountVoted(uint256 proposalId, address account) public view returns (bool) {
return proposals[proposalId].receipts[account].hasVoted;
}
/**
* @notice function to retrieve the multisig address
* @dev reasoning: if multisig changes we need governance to approve the next multisig address,
* so simply inherit in a governance upgrade from this function and set the new address
* @return the multisig address
*
*/
function returnMultisigAddress() public pure virtual returns (address) {
return 0xb04E030140b30C27bcdfaafFFA98C57d80eDa7B4;
}
/**
* @notice This should handle the logic of the external function
* @dev IMPORTANT: This function uses the gasCompensation modifier.
* as such this function can trigger a payable fallback.
* It is not possible to vote without revert more than once,
* without hasAccountVoted being true, eliminating gas refunds in this case.
* Gas compensation is also using the low level send(), forwarding 23000 gas
* as to disallow further logic execution above that threshold.
* @param from array of addresses that should have delegated to voter
* @param proposalId id of proposal account is voting on
* @param support true if yes false if no
* @param gasCompensated true if gas should be compensated (given all internal checks pass)
*
*/
function _castDelegatedVote(address[] memory from, uint256 proposalId, bool support, bool gasCompensated)
internal
gasCompensation(msg.sender, gasCompensated, (msg.sender == tx.origin ? 21e3 : 0))
{
for (uint256 i = 0; i < from.length; i++) {
address delegator = from[i];
require(
delegatedTo[delegator] == msg.sender || delegator == msg.sender, "Governance: not authorized"
);
require(!gasCompensated || !hasAccountVoted(proposalId, delegator), "Governance: voted already");
_castVote(delegator, proposalId, support);
}
}
}

@ -0,0 +1,43 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import { Governance } from "../v1/Governance.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { ITornadoVault } from "./interfaces/ITornadoVault.sol";
/// @title Version 2 Governance contract of the tornado.cash governance
contract GovernanceVaultUpgrade is Governance {
using SafeMath for uint256;
// vault which stores user TORN
ITornadoVault public immutable userVault;
// call Governance v1 constructor
constructor(address _userVault) public Governance() {
userVault = ITornadoVault(_userVault);
}
/// @notice Withdraws TORN from governance if conditions permit
/// @param amount the amount of TORN to withdraw
function unlock(uint256 amount) public virtual override {
require(getBlockTimestamp() > canWithdrawAfter[msg.sender], "Governance: tokens are locked");
lockedBalance[msg.sender] = lockedBalance[msg.sender].sub(amount, "Governance: insufficient balance");
userVault.withdrawTorn(msg.sender, amount);
}
/// @notice checker for success on deployment
/// @return returns precise version of governance
function version() external pure virtual returns (string memory) {
return "2.vault-migration";
}
/// @notice transfers tokens from the contract to the vault, withdrawals are unlock()
/// @param owner account/contract which (this) spender will send to the user vault
/// @param amount amount which spender will send to the user vault
function _transferTokens(address owner, uint256 amount) internal virtual override {
require(torn.transferFrom(owner, address(userVault), amount), "TORN: transferFrom failed");
lockedBalance[owner] = lockedBalance[owner].add(amount);
}
}

@ -0,0 +1,29 @@
# Tornado Governance Changes
Governance proposal [repo](https://github.com/peppersec/tornado-vault-and-gas-proposal).
## Governance Vault Upgrade (GovernanceVaultUpgrade.sol)
`GovernanceVaultUpgrade` is the first major upgrade to tornado governance. This upgrade introduces new logic which is used to communicate with `TornVault` from the governance contract. The motivation behind this upgrade:
- split DAO member locked TORN from vesting locked TORN.
- block Governance from being able to interact with user TORN.
To solve point 1 of the formerly stated problems, and to reduce the logic bloat of the lock and unlock functionalities, we have opted for calculating the amount of user TORN locked in the governance contract. The calculations and explanations may be found [here](https://github.com/h-ivor/tornado-lottery-period/blob/final_with_auction/scripts/balance_estimation.md).
### Additions and changes
| Function/variable signature | is addition or change? | describe significance |
| ---------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `_transferTokens(address,uint256)` | change | instead of transferring to the governance contract, funds are now transferred to the torn vault with a `transferFrom` call, this has an effect on both the `lock` and `lockWithApproval` function |
| `unlock(uint256)` | change | unlock now triggers `withdrawTorn(address,uint256)` within the vault which reverts on an unsuccessful transfer (safeTransfer) |
| `version` | addition | tells current version of governance contract |
| `address immutable userVault` | addition | address of the deployed vault |
### Tornado Vault (TornadoVault.sol)
The compliment to the above upgrade. Stores user TORN, does not keep records of it. Serves exclusively for deposits and withdrawals. Works in effect as personal store of TORN for a user with the balance being user for voting. Locking mechanisms are still in effect.
| Function/variable signature | describe significance |
| ------------------------------- | --------------------------------------------------- |
| `withdrawTorn(address,uint256)` | used for withdrawing TORN balance to users' account |

@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
interface ITornadoVault {
function withdrawTorn(address recipient, uint256 amount) external;
}

@ -0,0 +1 @@

@ -0,0 +1,59 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import { GovernanceGasUpgrade } from "../v2-vault-and-gas/GovernanceGasUpgrade.sol";
import { ITornadoStakingRewards } from "./interfaces/ITornadoStakingRewards.sol";
/**
* @notice The Governance staking upgrade. Adds modifier to any un/lock operation to update rewards
* @dev CONTRACT RISKS:
* - if updateRewards reverts (should not happen due to try/catch) locks/unlocks could be blocked
* - generally inherits risks from former governance upgrades
*/
contract GovernanceStakingUpgrade is GovernanceGasUpgrade {
ITornadoStakingRewards public immutable Staking;
event RewardUpdateSuccessful(address indexed account);
event RewardUpdateFailed(address indexed account, bytes indexed errorData);
constructor(address stakingRewardsAddress, address gasCompLogic, address userVaultAddress)
public
GovernanceGasUpgrade(gasCompLogic, userVaultAddress)
{
Staking = ITornadoStakingRewards(stakingRewardsAddress);
}
/**
* @notice This modifier should make a call to Staking to update the rewards for account without impacting logic on revert
* @dev try / catch block to handle reverts
* @param account Account to update rewards for.
*
*/
modifier updateRewards(address account) {
try Staking.updateRewardsOnLockedBalanceChange(account, lockedBalance[account]) {
emit RewardUpdateSuccessful(account);
} catch (bytes memory errorData) {
emit RewardUpdateFailed(account, errorData);
}
_;
}
function lock(address owner, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
public
virtual
override
updateRewards(owner)
{
super.lock(owner, amount, deadline, v, r, s);
}
function lockWithApproval(uint256 amount) public virtual override updateRewards(msg.sender) {
super.lockWithApproval(amount);
}
function unlock(uint256 amount) public virtual override updateRewards(msg.sender) {
super.unlock(amount);
}
}

@ -0,0 +1,14 @@
# Tornado Relayer Registry
Governance proposal [repo](https://github.com/Rezan-vm/tornado-relayer-registry).
Governance upgrade which includes a registry for relayer registration and staking mechanisms for the TORN token.
## Overview
1. Anyone can become a relayer by staking TORN into Registry contract.
2. Minimum stake is governed by the Governance.
3. Each Pool has its own fee % which is also set by the Governance.
4. On every withdrawal via relayer, the relayer has to pay the Tornado Pool fee in TORN. The fee is deducted from his staked balance.
5. All collected fees are stored into StakingReward contract.
6. Any TORN holder can stake their TORN into Governance contract like they were before, but earning fees proportionately to their stake.

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

@ -0,0 +1,24 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
import "@openzeppelin/contracts/proxy/TransparentUpgradeableProxy.sol";
/**
* @dev TransparentUpgradeableProxy where admin is allowed to call implementation methods.
*/
contract AdminUpgradeableProxy is TransparentUpgradeableProxy {
/**
* @dev Initializes an upgradeable proxy backed by the implementation at `_logic`.
*/
constructor(address _logic, address _admin, bytes memory _data)
public
payable
TransparentUpgradeableProxy(_logic, _admin, _data)
{ }
/**
* @dev Override to allow admin access the fallback function.
*/
function _beforeFallback() internal override { }
}

@ -0,0 +1,73 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import "../v1/Governance.sol";
import "../v3-relayer-registry/GovernanceStakingUpgrade.sol";
contract GovernanceExploitPatchUpgrade is GovernanceStakingUpgrade {
mapping(uint256 => bytes32) public proposalCodehashes;
constructor(
address stakingRewardsAddress,
address gasCompLogic,
address userVaultAddress
) public GovernanceStakingUpgrade(stakingRewardsAddress, gasCompLogic, userVaultAddress) {}
/// @notice Return the version of the contract
function version() external pure virtual override returns (string memory) {
return "4.patch-exploit";
}
/**
* @notice Execute a proposal
* @dev This upgrade should protect against Metamorphic contracts by comparing the proposal's extcodehash with a stored one
* @param proposalId The proposal's ID
*/
function execute(uint256 proposalId) public payable virtual override(Governance) {
require(msg.sender != address(this), "Governance::propose: pseudo-external function");
Proposal storage proposal = proposals[proposalId];
address target = proposal.target;
bytes32 proposalCodehash;
assembly {
proposalCodehash := extcodehash(target)
}
require(
proposalCodehash == proposalCodehashes[proposalId],
"Governance::propose: metamorphic contracts not allowed"
);
super.execute(proposalId);
}
/**
* @notice Internal function called from propoese
* @dev This should store the extcodehash of the proposal contract
* @param proposer proposer address
* @param target smart contact address that will be executed as result of voting
* @param description description of the proposal
* @return proposalId new proposal id
*/
function _propose(
address proposer,
address target,
string memory description
) internal virtual override(Governance) returns (uint256 proposalId) {
// Implies all former predicates were valid
proposalId = super._propose(proposer, target, description);
bytes32 proposalCodehash;
assembly {
proposalCodehash := extcodehash(target)
}
proposalCodehashes[proposalId] = proposalCodehash;
}
}

@ -0,0 +1,42 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import "../v1/Governance.sol";
import "../v4-exploit-patch/GovernanceExploitPatchUpgrade.sol";
contract GovernanceProposalStateUpgrade is GovernanceExploitPatchUpgrade {
constructor(
address stakingRewardsAddress,
address gasCompLogic,
address userVaultAddress
) public GovernanceExploitPatchUpgrade(stakingRewardsAddress, gasCompLogic, userVaultAddress) {}
/// @notice Return the version of the contract
function version() external pure virtual override returns (string memory) {
return "5.proposal-state-patch";
}
function state(uint256 proposalId) public view virtual override returns (ProposalState) {
require(proposalId <= proposalCount() && proposalId > 0, "Governance::state: invalid proposal id");
Proposal storage proposal = proposals[proposalId];
if (getBlockTimestamp() <= proposal.startTime) {
return ProposalState.Pending;
} else if (getBlockTimestamp() <= proposal.endTime) {
return ProposalState.Active;
} else if (proposal.executed) {
return ProposalState.Executed;
} else if (
proposal.forVotes <= proposal.againstVotes || proposal.forVotes + proposal.againstVotes < QUORUM_VOTES
) {
return ProposalState.Defeated;
} else if (getBlockTimestamp() >= proposal.endTime.add(EXECUTION_DELAY).add(EXECUTION_EXPIRATION)) {
return ProposalState.Expired;
} else if (getBlockTimestamp() >= proposal.endTime.add(EXECUTION_DELAY)) {
return ProposalState.AwaitingExecution;
} else {
return ProposalState.Timelocked;
}
}
}

57
hardhat.config.js Normal file

@ -0,0 +1,57 @@
require("@nomicfoundation/hardhat-toolbox");
require("hardhat-gas-reporter");
require("dotenv").config();
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: {
compilers: [
{
version: "0.8.20",
},
{
version: "0.6.12",
},
],
},
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
mocha: {
timeout: 100000000,
},
networks: {
mainnet: {
url: "https://ethereum.publicnode.com",
accounts: [process.env.REAL_PK],
},
testnet: {
url: "https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161",
accounts: [process.env.TEST_PK],
},
hardhat: {
forking: {
url: "https://rpc.mevblocker.io/fast",
enabled: true,
blockNumber: 19062612,
accounts: [process.env.REAL_PK],
},
chainId: 1,
accounts: [
{
privateKey: process.env.REAL_PK,
balance: "10000000000000000000000000000000",
},
],
},
},
etherscan: {
apiKey: process.env.ETHERSCAN_KEY,
},
gasReporter: {
enabled: false,
},
};

17738
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

46
package.json Normal file

@ -0,0 +1,46 @@
{
"name": "proposal-46",
"version": "1.0.0",
"description": "",
"main": "hardhat.config.js",
"directories": {
"test": "test"
},
"scripts": {
"test": "npx hardhat test",
"deploy": "npx hardhat run --network mainnet scripts/deploy.js"
},
"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",
"chai-things": "^0.2.0",
"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",
"base58-solidity": "^1.0.2",
"content-hash": "^2.5.2",
"torn-token": "^1.0.8",
"tornado-governance": "^1.0.3"
}
}

48
scripts/deploy.js Normal file

@ -0,0 +1,48 @@
// 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 stakingAddr = "0x5B3f656C80E8ddb9ec01Dd9018815576E9238c29";
const gasCompensationAddr = "0xFA4C1f3f7D5dd7c12a9Adb82Cd7dDA542E3d59ef";
const userVaultAddr = "0x2F50508a8a3D323B91336FA3eA6ae50E55f32185";
const governanceFactory = await ethers.getContractFactory("GovernanceProposalStateUpgrade");
const governanceImpl = await governanceFactory.deploy(stakingAddr, gasCompensationAddr, userVaultAddr);
const governanceImplAddr = await governanceImpl.getAddress();
let tx = governanceImpl.deploymentTransaction();
await tx.wait(16);
console.log("New governance impl deployment confirmed with 16 blocks, waiting for verification on Etherscan");
await hre.run("verify:verify", {
address: governanceImplAddr,
contract: "contracts/v5-proposal-state-patch/GovernanceProposalStateUpgrade.sol:GovernanceProposalStateUpgrade",
constructorArguments: [stakingAddr, gasCompensationAddr, userVaultAddr],
});
const proposalFactory = await ethers.getContractFactory("Proposal");
const proposal = await proposalFactory.deploy(governanceImplAddr);
const deployedProposalAddr = await proposal.getAddress();
console.log(`Proposal contract deployed by address ${deployedProposalAddr}, waiting for blockchain confirmations...`);
tx = proposal.deploymentTransaction();
await tx.wait(16);
console.log("Deployment confirmed with 16 blocks, waiting for verification on Etherscan");
await hre.run("verify:verify", {
address: deployedProposalAddr,
contract: "contracts/Proposal.sol:Proposal",
constructorArguments: [governanceImplAddr],
});
}
// 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;
});

31
scripts/testDeploy.js Normal file

@ -0,0 +1,31 @@
// 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 proposalFactory = await ethers.getContractFactory("TestProposal");
const proposal = await proposalFactory.deploy();
const deployedProposalAddr = await proposal.getAddress();
console.log(`Proposal contract deployed by address ${deployedProposalAddr}, waiting for blockchain confirmations...`);
tx = proposal.deploymentTransaction();
await tx.wait(16);
console.log("Deployment confirmed with 16 blocks, waiting for verification on Etherscan");
await hre.run("verify:verify", {
address: deployedProposalAddr,
contract: "contracts/Test.sol:TestProposal",
});
}
// 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;
});

352
test/Governance.js Normal file

@ -0,0 +1,352 @@
const { ethers, network } = require("hardhat");
const { time } = require("@nomicfoundation/hardhat-toolbox/network-helpers");
const { expect } = require("chai");
const {
deployAndExecuteProposal,
getProxyImplAddr,
governanceAddr,
resetStateBeforeProposal,
getManyEth,
resolveAddr,
getTorn,
getGovernance,
getPermitSignature,
tornAddr,
stakingAddr,
executeNewProposal,
signerLockInGov,
} = require("./utils");
// comment some checks if your proposal changes something
describe("All Governance checks", function () {
beforeEach(resetStateBeforeProposal);
it("Governance implementation should be a valid contract", async function () {
await deployAndExecuteProposal();
const governanceImplAddr = await getProxyImplAddr(governanceAddr);
const code = await ethers.provider.getCode(governanceImplAddr);
expect(code).to.not.be.equal("0x");
});
async function createValidProposal() {
// this proposal changes governance impl addr to old governance impl 0xBa178126C28F50Ee60322a82f5EbCd6b3711e101
const proposalBytecode =
"0x608060405234801561001057600080fd5b50610161806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063373058b814610030575b600080fd5b61003861003a565b005b735efda50f22d34f262c29268506c5fa42cb56a1ce73ffffffffffffffffffffffffffffffffffffffff16633659cfe673ba178126c28f50ee60322a82f5ebcd6b3711e1016040518263ffffffff1660e01b815260040161009b9190610110565b600060405180830381600087803b1580156100b557600080fd5b505af11580156100c9573d6000803e3d6000fd5b50505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100fa826100cf565b9050919050565b61010a816100ef565b82525050565b60006020820190506101256000830184610101565b9291505056fea264697066735822122071ee22910809ebd1174acbe4a8ce386abb553bbc1cf37fd7c9abb166b18ca6f664736f6c63430008140033";
const signer = (await ethers.getSigners())[0];
const proposalAbi = ["function executeProposal()"];
const proposalFactory = new ethers.ContractFactory(proposalAbi, proposalBytecode, signer);
const proposalContract = await proposalFactory.deploy();
const proposalAddr = await proposalContract.getAddress();
return proposalAddr;
}
it("Governance contract should be updatable", async function () {
await deployAndExecuteProposal();
const someOldGovernanceImpl = "0xBa178126C28F50Ee60322a82f5EbCd6b3711e101";
const proposalAddr = await createValidProposal();
await executeNewProposal(proposalAddr);
const governance = await getGovernance();
const lastProposalId = await governance.proposalCount();
expect(await governance.state(lastProposalId)).to.be.equal(5); // executed
expect(await getProxyImplAddr(governanceAddr)).to.be.equal(someOldGovernanceImpl);
});
it("Proposal count should change by one", async function () {
const governance = await getGovernance();
const lastProposal = await governance.proposalCount();
await deployAndExecuteProposal();
expect(await governance.proposalCount()).to.be.equal(lastProposal + 1n);
});
it("Stakers should be able to lock and unlock", async function () {
const signer = (await ethers.getSigners())[0];
const governanceSigner = await ethers.getImpersonatedSigner(governanceAddr);
const governance = await getGovernance(signer);
const torn = await getTorn(governanceSigner);
const thousandTorns = 1000n * 10n ** 18n;
const balanceBefore = await torn.balanceOf(signer.address);
const lockedBalanceBefore = await governance.lockedBalance(signer.address);
await deployAndExecuteProposal();
await torn.transfer(signer.address, thousandTorns);
const deadline = ethers.MaxUint256;
const { v, r, s } = await getPermitSignature(signer, torn, governanceAddr, thousandTorns, deadline);
await governance.lock(signer.address, thousandTorns, deadline, v, r, s);
const lockedBalanceAfter = await governance.lockedBalance(signer.address);
expect(lockedBalanceAfter - lockedBalanceBefore).to.be.equal(thousandTorns);
await governance.unlock(thousandTorns);
const balanceAfter = await torn.balanceOf(signer.address);
expect(balanceAfter - balanceBefore).to.be.equal(thousandTorns);
});
it("Staker locked balance should not change", async function () {
const me = await resolveAddr("butterfly-attractor.eth");
const governance = await getGovernance();
const lockedBalanceBefore = await governance.lockedBalance(me);
await deployAndExecuteProposal();
expect(await governance.lockedBalance(me)).to.be.equal(lockedBalanceBefore);
});
it("Proposals info should not change", async function () {
const governance = await getGovernance();
const lastProposal = await governance.proposalCount();
const proposalsBefore = await Promise.all(
Array.from({ length: Number(lastProposal) - 1 }, (_, i) => i + 1).map((i) => governance.proposals(i)),
);
await deployAndExecuteProposal();
const proposalsAfter = await Promise.all(
Array.from({ length: Number(lastProposal) - 1 }, (_, i) => i + 1).map((i) => governance.proposals(i)),
);
expect(proposalsAfter).to.deep.equal(proposalsBefore);
});
// it("Proposals state should not change", async function(){
// const governance = await getGovernance();
// const lastProposal = await governance.proposalCount();
// const proposalStatesBefore = await Promise.all(
// Array.from({ length: Number(lastProposal) - 1 }, (_, i) => i + 1).map((i) => governance.state(i)),
// );
// await deployAndExecuteProposal();
// const proposalStatesAfter = await Promise.all(
// Array.from({ length: Number(lastProposal) - 1 }, (_, i) => i + 1).map((i) => governance.state(i)),
// );
// expect(proposalStatesAfter).to.deep.equal(proposalStatesBefore);
// })
it("Quorum votes should not change", async function () {
const governance = await getGovernance();
const quorum = await governance.QUORUM_VOTES();
await deployAndExecuteProposal();
expect(await governance.QUORUM_VOTES()).to.be.equal(quorum);
});
it("Closing period should not change", async function () {
const governance = await getGovernance();
const closingPeriod = await governance.CLOSING_PERIOD();
await deployAndExecuteProposal();
expect(await governance.CLOSING_PERIOD()).to.be.equal(closingPeriod);
});
it("Execution delay should not change", async function () {
const governance = await getGovernance();
const executionDelay = await governance.EXECUTION_DELAY();
await deployAndExecuteProposal();
expect(await governance.EXECUTION_DELAY()).to.be.equal(executionDelay);
});
it("Execution expiration should not change", async function () {
const governance = await getGovernance();
const expirationPeriod = await governance.EXECUTION_EXPIRATION();
await deployAndExecuteProposal();
expect(await governance.EXECUTION_EXPIRATION()).to.be.equal(expirationPeriod);
});
it("Proposal initiation threshold should not change", async function () {
const governance = await getGovernance();
const proposalThreshold = await governance.PROPOSAL_THRESHOLD();
await deployAndExecuteProposal();
expect(await governance.PROPOSAL_THRESHOLD()).to.be.equal(proposalThreshold);
});
it("Voting extend time should not change", async function () {
const governance = await getGovernance();
const votingExtendTime = await governance.VOTE_EXTEND_TIME();
await deployAndExecuteProposal();
expect(await governance.VOTE_EXTEND_TIME()).to.be.equal(votingExtendTime);
});
it("Voting initiation delay should not change", async function () {
const governance = await getGovernance();
const proposalVotingDelay = await governance.VOTING_DELAY();
await deployAndExecuteProposal();
expect(await governance.VOTING_DELAY()).to.be.equal(proposalVotingDelay);
});
it("Voting period should not change", async function () {
const governance = await getGovernance();
const votingPeriod = await governance.VOTING_PERIOD();
await deployAndExecuteProposal();
expect(await governance.VOTING_PERIOD()).to.be.equal(votingPeriod);
});
it("User vault address should not change", async function () {
const governance = await getGovernance();
const vaultAddr = await governance.userVault();
const vaultCode = await ethers.provider.getCode(vaultAddr);
expect(vaultCode).to.be.not.equal("0x");
await deployAndExecuteProposal();
expect(await governance.userVault()).to.be.equal(vaultAddr);
expect(await ethers.provider.getCode(vaultAddr)).to.be.equal(vaultCode);
});
it("Torn address should not be reinitialized", async function () {
const { governanceContract } = await deployAndExecuteProposal();
expect(await governanceContract.torn()).to.be.equal(tornAddr);
});
it("Staking address should not change", async function () {
const { governanceContract } = await deployAndExecuteProposal();
expect(await governanceContract.Staking()).to.be.equal(stakingAddr);
});
it("Gas compensator address should not change", async function () {
const governance = await getGovernance();
const gasCompensatorAddr = await governance.gasCompensationVault();
await deployAndExecuteProposal();
expect(await governance.gasCompensationVault()).to.be.equal(gasCompensatorAddr);
});
async function delegate(tornAmount, signer) {
const governance = await getGovernance();
if (!tornAmount) tornAmount = await governance.QUORUM_VOTES();
if (!signer) signer = (await ethers.getSigners())[0];
const governanceContract = await getGovernance(signer);
const zer0 = "0x000000000000000000000000000000000000dEaD";
await signerLockInGov(tornAmount, signer);
await governanceContract.delegate(zer0);
return { signer, delegated: await ethers.getImpersonatedSigner(zer0), governance: governanceContract };
}
it("Delegators state should not change", async function () {
await deployAndExecuteProposal();
const { signer, governance, delegated } = await delegate();
expect(await governance.delegatedTo(signer.address)).to.be.equal(delegated.address);
});
it("Delegation should work", async function () {
await deployAndExecuteProposal();
const { signer, governance, delegated } = await delegate();
expect(await governance.delegatedTo(signer.address)).to.be.equal(delegated.address);
});
it("Delegator should be able to cast vote", async function () {
await deployAndExecuteProposal();
const proposalAddr = await createValidProposal();
const gov = await getGovernance();
await gov.propose(proposalAddr, "");
const { governance, delegated, signer } = await delegate();
const proposalId = await governance.proposalCount();
await time.increase(60 * 60);
const governanceDelegated = governance.connect(delegated);
await governanceDelegated.castDelegatedVote([signer.address], proposalId, true);
await time.increase(60 * 60 * 24 * 7 + 60);
expect(await governance.state(proposalId)).to.be.equal(4); // awaiting execution
});
it("Delegator should be able to propose", async function () {
await deployAndExecuteProposal();
const { governance, delegated, signer } = await delegate();
const proposalAddr = await createValidProposal();
const governanceDelegated = governance.connect(delegated);
await governanceDelegated.proposeByDelegate(signer.address, proposalAddr, "");
const proposalId = await governance.proposalCount();
await time.increase(60 * 60);
await expect(governanceDelegated.castVote(proposalId, true)).to.be.revertedWith("Governance: balance is 0");
await governanceDelegated.castDelegatedVote([signer.address], proposalId, true);
await time.increase(60 * 60 * 24 * 7 + 60);
await governanceDelegated.execute(proposalId);
expect(await governance.state(proposalId)).to.be.equal(5); // executed
});
it("Staking rewards should be distributed without problem", async function () {
const { governanceContract } = await deployAndExecuteProposal();
const stakingVault = await governanceContract.userVault();
const torn = await getTorn();
const manyTorns = 100n * 1000n * 10n ** 18n;
const signer = await signerLockInGov(manyTorns);
const signerLockedBalance = await governanceContract.lockedBalance(signer.address);
const sumStaking = await torn.balanceOf(stakingVault);
const toDistibute = 10000n * 18n ** 18n;
const stakingAddr = await governanceContract.Staking();
const governanceSigner = await ethers.getImpersonatedSigner(governanceAddr);
const stakingContract = await ethers.getContractAt(
require("./abi/staking.abi.json"),
stakingAddr,
governanceSigner,
);
const ratioConstant = await stakingContract.ratioConstant();
const expectedReward = (toDistibute * ((signerLockedBalance * ratioConstant) / sumStaking)) / ratioConstant;
const rewardsBeforeDistribute = await stakingContract.checkReward(signer.address);
await stakingContract.addBurnRewards(toDistibute);
const rewardsAfterDistibute = await stakingContract.checkReward(signer.address);
expect((rewardsAfterDistibute - rewardsBeforeDistribute) / 1000n).to.be.equal(expectedReward / 1000n);
});
it("Check if account voted in proposal should work correct", async function () {
await deployAndExecuteProposal();
const proposalAddr = await createValidProposal();
const signer = await signerLockInGov("quorum");
const governance = await getGovernance(signer);
let proposalId = await governance.proposalCount();
await governance.propose(proposalAddr, "");
expect(await governance.hasAccountVoted(proposalId + 1n, signer.address)).to.be.equal(false);
proposalId = await governance.proposalCount();
expect(await governance.hasAccountVoted(proposalId, signer.address)).to.be.equal(false);
await time.increase(60 * 60);
expect(await governance.hasAccountVoted(proposalId, signer.address)).to.be.equal(false);
await governance.castVote(proposalId, true);
expect(await governance.hasAccountVoted(proposalId, signer.address)).to.be.equal(true);
});
it("Staker should be able to change vote", async function () {
const { governanceContract } = await deployAndExecuteProposal();
const halfQuorum = (await governanceContract.QUORUM_VOTES()) / 2n;
const proposalAddr = await createValidProposal();
const signer = await signerLockInGov(halfQuorum);
const lockedBalance = await governanceContract.lockedBalance(signer.address);
const governance = await getGovernance(signer);
await governance.propose(proposalAddr, "");
const proposalId = await governance.proposalCount();
await time.increase(60 * 60);
await governance.castVote(proposalId, true);
let proposalInfo = await governance.proposals(proposalId);
expect(proposalInfo.forVotes).to.be.equal(lockedBalance);
expect(proposalInfo.againstVotes).to.be.equal(0n);
await governance.castVote(proposalId, false);
proposalInfo = await governance.proposals(proposalId);
expect(proposalInfo.forVotes).to.be.equal(0n);
expect(proposalInfo.againstVotes).to.be.equal(lockedBalance);
});
it("Delegator should not be able to vote twice in one proposal", async function () {
const { governanceContract } = await deployAndExecuteProposal();
const proposalAddr = await createValidProposal();
const halfQuorum = (await governanceContract.QUORUM_VOTES()) / 2n;
const { signer, governance, delegated } = await delegate(halfQuorum);
await governance.propose(proposalAddr, "");
const proposalId = await governance.proposalCount();
await time.increase(60 * 60);
await governance.castVote(proposalId, true);
const governanceDelegated = governance.connect(delegated);
await expect(governanceDelegated.castDelegatedVote([signer.address], proposalId, true)).to.be.revertedWith(
"Governance: voted already",
);
});
it("If staker change vote in last hour voting should prolong", async function () {
await deployAndExecuteProposal();
const proposalAddr = await createValidProposal();
const signer = await signerLockInGov("quorum");
const governance = await getGovernance(signer);
await governance.propose(proposalAddr, "");
const proposalId = await governance.proposalCount();
await time.increase(60 * 10);
await governance.castVote(proposalId, true);
await time.increase(60 * 60 * 24 * 4 + 60 * 60 * 23 + 60 * 30);
let proposalInfo = await governance.proposals(proposalId);
expect(proposalInfo.extended).to.be.equal(false);
await governance.castVote(proposalId, false);
proposalInfo = await governance.proposals(proposalId);
expect(proposalInfo.extended).to.be.equal(true);
});
});

82
test/Proposal.js Normal file

@ -0,0 +1,82 @@
const { expect } = require("chai");
const contentHash = require('content-hash')
const {
resetStateBeforeProposal,
deployAndExecuteProposal,
governanceAddr,
getProxyImplAddr,
getGovernance,
getEnsResolver
} = require("./utils");
describe("Proposal results check", function () {
beforeEach(resetStateBeforeProposal);
it("IPFS contenthashes on ENS should be correct", async function(){
await deployAndExecuteProposal();
const docsEns = "docs.tornadocash.eth";
const docsSourceEns = "docs.sources.tornadocash.eth";
const docsIpfs = "QmYjBe2bNTGQzgdqAi7hkdNJ9BBrAgf4e8teRWLa4oN6Y6";
const docsSourceIpfs = "QmcdTqAMD4nvDhAfWkEnKCQHhFk8K7XmFDouextcfrjWif";
const ensResolver = await getEnsResolver(docsEns);
expect(contentHash.decode(await ensResolver.contenthash(ethers.namehash(docsEns)))).to.be.equal(docsIpfs);
expect(contentHash.decode(await ensResolver.contenthash(ethers.namehash(docsSourceEns)))).to.be.equal(docsSourceIpfs);
})
it("Governance implementation should be updated", async function () {
const currentImplAddr = "0xBa178126C28F50Ee60322a82f5EbCd6b3711e101";
const { newGovernanceImplAddr } = await deployAndExecuteProposal();
const implementationAddr = await getProxyImplAddr(governanceAddr);
expect(implementationAddr).to.equal(newGovernanceImplAddr);
expect(implementationAddr).to.not.be.equal(currentImplAddr);
});
it("Governance version should be updated", async function () {
const { governanceContract } = await deployAndExecuteProposal();
expect(await governanceContract.version()).to.be.equal("5.proposal-state-patch");
});
async function getProposalInfo(governance, proposalId) {
const proposalData = await governance.proposals(proposalId);
const state = await governance.state(proposalId);
return { data: proposalData, status: Number(state) };
}
function getProposalQuorum(proposalInfo) {
return proposalInfo.data[4];
}
function isProposalExecuted(proposalInfo) {
return proposalInfo.data[6];
}
it("Proposal states should be correct", async function () {
const governance = await getGovernance();
const lastProposal = await governance.proposalCount();
const oldProposals = await Promise.all(
Array.from({ length: Number(lastProposal) - 1 }, (_, i) => i + 1).map((i) => getProposalInfo(governance, i)),
);
const { governanceContract } = await deployAndExecuteProposal();
const proposals = await Promise.all(
Array.from({ length: Number(lastProposal) - 1 }, (_, i) => i + 1).map((i) =>
getProposalInfo(governanceContract, i),
),
);
for (const [id, proposalInfo] of proposals.entries()) {
const oldProposalInfo = oldProposals[id];
if (
oldProposalInfo.status == 2 &&
getProposalQuorum(oldProposalInfo) > 25000n * 10n ** 18n &&
isProposalExecuted(oldProposalInfo)
) {
expect(proposalInfo.status).to.be.equal(5);
continue;
}
expect(oldProposalInfo.status).to.be.equal(proposalInfo.status);
}
});
});

@ -0,0 +1,274 @@
[
{
"inputs": [
{ "internalType": "contract ENS", "name": "_old", "type": "address" }
],
"payable": false,
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "operator",
"type": "address"
},
{
"indexed": false,
"internalType": "bool",
"name": "approved",
"type": "bool"
}
],
"name": "ApprovalForAll",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "node",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "bytes32",
"name": "label",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "NewOwner",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "node",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "address",
"name": "resolver",
"type": "address"
}
],
"name": "NewResolver",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "node",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "uint64",
"name": "ttl",
"type": "uint64"
}
],
"name": "NewTTL",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "node",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "Transfer",
"type": "event"
},
{
"constant": true,
"inputs": [
{ "internalType": "address", "name": "owner", "type": "address" },
{ "internalType": "address", "name": "operator", "type": "address" }
],
"name": "isApprovedForAll",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "old",
"outputs": [
{ "internalType": "contract ENS", "name": "", "type": "address" }
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" }
],
"name": "owner",
"outputs": [{ "internalType": "address", "name": "", "type": "address" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" }
],
"name": "recordExists",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" }
],
"name": "resolver",
"outputs": [{ "internalType": "address", "name": "", "type": "address" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{ "internalType": "address", "name": "operator", "type": "address" },
{ "internalType": "bool", "name": "approved", "type": "bool" }
],
"name": "setApprovalForAll",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" },
{ "internalType": "address", "name": "owner", "type": "address" }
],
"name": "setOwner",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" },
{ "internalType": "address", "name": "owner", "type": "address" },
{ "internalType": "address", "name": "resolver", "type": "address" },
{ "internalType": "uint64", "name": "ttl", "type": "uint64" }
],
"name": "setRecord",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" },
{ "internalType": "address", "name": "resolver", "type": "address" }
],
"name": "setResolver",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" },
{ "internalType": "bytes32", "name": "label", "type": "bytes32" },
{ "internalType": "address", "name": "owner", "type": "address" }
],
"name": "setSubnodeOwner",
"outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" },
{ "internalType": "bytes32", "name": "label", "type": "bytes32" },
{ "internalType": "address", "name": "owner", "type": "address" },
{ "internalType": "address", "name": "resolver", "type": "address" },
{ "internalType": "uint64", "name": "ttl", "type": "uint64" }
],
"name": "setSubnodeRecord",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" },
{ "internalType": "uint64", "name": "ttl", "type": "uint64" }
],
"name": "setTTL",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" }
],
"name": "ttl",
"outputs": [{ "internalType": "uint64", "name": "", "type": "uint64" }],
"payable": false,
"stateMutability": "view",
"type": "function"
}
]

@ -0,0 +1,671 @@
[
{
"inputs": [
{ "internalType": "contract ENS", "name": "_ens", "type": "address" },
{
"internalType": "contract INameWrapper",
"name": "wrapperAddress",
"type": "address"
},
{
"internalType": "address",
"name": "_trustedETHController",
"type": "address"
},
{
"internalType": "address",
"name": "_trustedReverseRegistrar",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "node",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "uint256",
"name": "contentType",
"type": "uint256"
}
],
"name": "ABIChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "node",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "address",
"name": "a",
"type": "address"
}
],
"name": "AddrChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "node",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "uint256",
"name": "coinType",
"type": "uint256"
},
{
"indexed": false,
"internalType": "bytes",
"name": "newAddress",
"type": "bytes"
}
],
"name": "AddressChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "operator",
"type": "address"
},
{
"indexed": false,
"internalType": "bool",
"name": "approved",
"type": "bool"
}
],
"name": "ApprovalForAll",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "bytes32",
"name": "node",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "address",
"name": "delegate",
"type": "address"
},
{
"indexed": true,
"internalType": "bool",
"name": "approved",
"type": "bool"
}
],
"name": "Approved",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "node",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "bytes",
"name": "hash",
"type": "bytes"
}
],
"name": "ContenthashChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "node",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "bytes",
"name": "name",
"type": "bytes"
},
{
"indexed": false,
"internalType": "uint16",
"name": "resource",
"type": "uint16"
},
{
"indexed": false,
"internalType": "bytes",
"name": "record",
"type": "bytes"
}
],
"name": "DNSRecordChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "node",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "bytes",
"name": "name",
"type": "bytes"
},
{
"indexed": false,
"internalType": "uint16",
"name": "resource",
"type": "uint16"
}
],
"name": "DNSRecordDeleted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "node",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "bytes",
"name": "lastzonehash",
"type": "bytes"
},
{
"indexed": false,
"internalType": "bytes",
"name": "zonehash",
"type": "bytes"
}
],
"name": "DNSZonehashChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "node",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "bytes4",
"name": "interfaceID",
"type": "bytes4"
},
{
"indexed": false,
"internalType": "address",
"name": "implementer",
"type": "address"
}
],
"name": "InterfaceChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "node",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "string",
"name": "name",
"type": "string"
}
],
"name": "NameChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "node",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "bytes32",
"name": "x",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "bytes32",
"name": "y",
"type": "bytes32"
}
],
"name": "PubkeyChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "node",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "string",
"name": "indexedKey",
"type": "string"
},
{
"indexed": false,
"internalType": "string",
"name": "key",
"type": "string"
},
{
"indexed": false,
"internalType": "string",
"name": "value",
"type": "string"
}
],
"name": "TextChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "node",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "uint64",
"name": "newVersion",
"type": "uint64"
}
],
"name": "VersionChanged",
"type": "event"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" },
{ "internalType": "uint256", "name": "contentTypes", "type": "uint256" }
],
"name": "ABI",
"outputs": [
{ "internalType": "uint256", "name": "", "type": "uint256" },
{ "internalType": "bytes", "name": "", "type": "bytes" }
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" }
],
"name": "addr",
"outputs": [
{ "internalType": "address payable", "name": "", "type": "address" }
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" },
{ "internalType": "uint256", "name": "coinType", "type": "uint256" }
],
"name": "addr",
"outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" },
{ "internalType": "address", "name": "delegate", "type": "address" },
{ "internalType": "bool", "name": "approved", "type": "bool" }
],
"name": "approve",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" }
],
"name": "clearRecords",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" }
],
"name": "contenthash",
"outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" },
{ "internalType": "bytes32", "name": "name", "type": "bytes32" },
{ "internalType": "uint16", "name": "resource", "type": "uint16" }
],
"name": "dnsRecord",
"outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" },
{ "internalType": "bytes32", "name": "name", "type": "bytes32" }
],
"name": "hasDNSRecords",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" },
{ "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" }
],
"name": "interfaceImplementer",
"outputs": [{ "internalType": "address", "name": "", "type": "address" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "owner", "type": "address" },
{ "internalType": "bytes32", "name": "node", "type": "bytes32" },
{ "internalType": "address", "name": "delegate", "type": "address" }
],
"name": "isApprovedFor",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "account", "type": "address" },
{ "internalType": "address", "name": "operator", "type": "address" }
],
"name": "isApprovedForAll",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes[]", "name": "data", "type": "bytes[]" }
],
"name": "multicall",
"outputs": [
{ "internalType": "bytes[]", "name": "results", "type": "bytes[]" }
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "nodehash", "type": "bytes32" },
{ "internalType": "bytes[]", "name": "data", "type": "bytes[]" }
],
"name": "multicallWithNodeCheck",
"outputs": [
{ "internalType": "bytes[]", "name": "results", "type": "bytes[]" }
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" }
],
"name": "name",
"outputs": [{ "internalType": "string", "name": "", "type": "string" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" }
],
"name": "pubkey",
"outputs": [
{ "internalType": "bytes32", "name": "x", "type": "bytes32" },
{ "internalType": "bytes32", "name": "y", "type": "bytes32" }
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }],
"name": "recordVersions",
"outputs": [{ "internalType": "uint64", "name": "", "type": "uint64" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" },
{ "internalType": "uint256", "name": "contentType", "type": "uint256" },
{ "internalType": "bytes", "name": "data", "type": "bytes" }
],
"name": "setABI",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" },
{ "internalType": "uint256", "name": "coinType", "type": "uint256" },
{ "internalType": "bytes", "name": "a", "type": "bytes" }
],
"name": "setAddr",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" },
{ "internalType": "address", "name": "a", "type": "address" }
],
"name": "setAddr",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "operator", "type": "address" },
{ "internalType": "bool", "name": "approved", "type": "bool" }
],
"name": "setApprovalForAll",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" },
{ "internalType": "bytes", "name": "hash", "type": "bytes" }
],
"name": "setContenthash",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" },
{ "internalType": "bytes", "name": "data", "type": "bytes" }
],
"name": "setDNSRecords",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" },
{ "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" },
{ "internalType": "address", "name": "implementer", "type": "address" }
],
"name": "setInterface",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" },
{ "internalType": "string", "name": "newName", "type": "string" }
],
"name": "setName",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" },
{ "internalType": "bytes32", "name": "x", "type": "bytes32" },
{ "internalType": "bytes32", "name": "y", "type": "bytes32" }
],
"name": "setPubkey",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" },
{ "internalType": "string", "name": "key", "type": "string" },
{ "internalType": "string", "name": "value", "type": "string" }
],
"name": "setText",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" },
{ "internalType": "bytes", "name": "hash", "type": "bytes" }
],
"name": "setZonehash",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" }
],
"name": "supportsInterface",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" },
{ "internalType": "string", "name": "key", "type": "string" }
],
"name": "text",
"outputs": [{ "internalType": "string", "name": "", "type": "string" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" }
],
"name": "zonehash",
"outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }],
"stateMutability": "view",
"type": "function"
}
]

@ -0,0 +1,636 @@
[
{
"inputs": [
{
"internalType": "address",
"name": "stakingRewardsAddress",
"type": "address"
},
{ "internalType": "address", "name": "gasCompLogic", "type": "address" },
{
"internalType": "address",
"name": "userVaultAddress",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "account",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
}
],
"name": "Delegated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "proposer",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "target",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "startTime",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "endTime",
"type": "uint256"
},
{
"indexed": false,
"internalType": "string",
"name": "description",
"type": "string"
}
],
"name": "ProposalCreated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "proposalId",
"type": "uint256"
}
],
"name": "ProposalExecuted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "account",
"type": "address"
},
{
"indexed": true,
"internalType": "bytes",
"name": "errorData",
"type": "bytes"
}
],
"name": "RewardUpdateFailed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "RewardUpdateSuccessful",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "account",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
}
],
"name": "Undelegated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "proposalId",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "voter",
"type": "address"
},
{
"indexed": true,
"internalType": "bool",
"name": "support",
"type": "bool"
},
{
"indexed": false,
"internalType": "uint256",
"name": "votes",
"type": "uint256"
}
],
"name": "Voted",
"type": "event"
},
{
"inputs": [],
"name": "CLOSING_PERIOD",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "EXECUTION_DELAY",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "EXECUTION_EXPIRATION",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "PROPOSAL_THRESHOLD",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "QUORUM_VOTES",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "Staking",
"outputs": [
{
"internalType": "contract ITornadoStakingRewards",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "VOTE_EXTEND_TIME",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "VOTING_DELAY",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "VOTING_PERIOD",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32[]", "name": "domains", "type": "bytes32[]" }
],
"name": "bulkResolve",
"outputs": [
{ "internalType": "address[]", "name": "result", "type": "address[]" }
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{ "internalType": "address", "name": "", "type": "address" }],
"name": "canWithdrawAfter",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "address[]", "name": "from", "type": "address[]" },
{ "internalType": "uint256", "name": "proposalId", "type": "uint256" },
{ "internalType": "bool", "name": "support", "type": "bool" }
],
"name": "castDelegatedVote",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "uint256", "name": "proposalId", "type": "uint256" },
{ "internalType": "bool", "name": "support", "type": "bool" }
],
"name": "castVote",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "uint256", "name": "proposalId", "type": "uint256" }
],
"name": "checkIfQuorumReached",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{ "internalType": "address", "name": "to", "type": "address" }],
"name": "delegate",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [{ "internalType": "address", "name": "", "type": "address" }],
"name": "delegatedTo",
"outputs": [{ "internalType": "address", "name": "", "type": "address" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "uint256", "name": "proposalId", "type": "uint256" }
],
"name": "execute",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "gasCompensationVault",
"outputs": [
{
"internalType": "contract IGasCompensationVault",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "uint256", "name": "proposalId", "type": "uint256" },
{ "internalType": "address", "name": "voter", "type": "address" }
],
"name": "getReceipt",
"outputs": [
{
"components": [
{ "internalType": "bool", "name": "hasVoted", "type": "bool" },
{ "internalType": "bool", "name": "support", "type": "bool" },
{ "internalType": "uint256", "name": "votes", "type": "uint256" }
],
"internalType": "struct Governance.Receipt",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "uint256", "name": "proposalId", "type": "uint256" },
{ "internalType": "address", "name": "account", "type": "address" }
],
"name": "hasAccountVoted",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "_torn", "type": "bytes32" }
],
"name": "initialize",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [{ "internalType": "address", "name": "", "type": "address" }],
"name": "latestProposalIds",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "owner", "type": "address" },
{ "internalType": "uint256", "name": "amount", "type": "uint256" },
{ "internalType": "uint256", "name": "deadline", "type": "uint256" },
{ "internalType": "uint8", "name": "v", "type": "uint8" },
{ "internalType": "bytes32", "name": "r", "type": "bytes32" },
{ "internalType": "bytes32", "name": "s", "type": "bytes32" }
],
"name": "lock",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "uint256", "name": "amount", "type": "uint256" }
],
"name": "lockWithApproval",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [{ "internalType": "address", "name": "", "type": "address" }],
"name": "lockedBalance",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"name": "proposalCodehashes",
"outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "proposalCount",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"name": "proposals",
"outputs": [
{ "internalType": "address", "name": "proposer", "type": "address" },
{ "internalType": "address", "name": "target", "type": "address" },
{ "internalType": "uint256", "name": "startTime", "type": "uint256" },
{ "internalType": "uint256", "name": "endTime", "type": "uint256" },
{ "internalType": "uint256", "name": "forVotes", "type": "uint256" },
{ "internalType": "uint256", "name": "againstVotes", "type": "uint256" },
{ "internalType": "bool", "name": "executed", "type": "bool" },
{ "internalType": "bool", "name": "extended", "type": "bool" }
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "target", "type": "address" },
{ "internalType": "string", "name": "description", "type": "string" }
],
"name": "propose",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "from", "type": "address" },
{ "internalType": "address", "name": "target", "type": "address" },
{ "internalType": "string", "name": "description", "type": "string" }
],
"name": "proposeByDelegate",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" }
],
"name": "resolve",
"outputs": [{ "internalType": "address", "name": "", "type": "address" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "returnMultisigAddress",
"outputs": [{ "internalType": "address", "name": "", "type": "address" }],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{ "internalType": "uint256", "name": "closingPeriod", "type": "uint256" }
],
"name": "setClosingPeriod",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "uint256", "name": "executionDelay", "type": "uint256" }
],
"name": "setExecutionDelay",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "executionExpiration",
"type": "uint256"
}
],
"name": "setExecutionExpiration",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "gasCompensationsLimit",
"type": "uint256"
}
],
"name": "setGasCompensations",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "proposalThreshold",
"type": "uint256"
}
],
"name": "setProposalThreshold",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "uint256", "name": "quorumVotes", "type": "uint256" }
],
"name": "setQuorumVotes",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "uint256", "name": "voteExtendTime", "type": "uint256" }
],
"name": "setVoteExtendTime",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "uint256", "name": "votingDelay", "type": "uint256" }
],
"name": "setVotingDelay",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "uint256", "name": "votingPeriod", "type": "uint256" }
],
"name": "setVotingPeriod",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "uint256", "name": "proposalId", "type": "uint256" }
],
"name": "state",
"outputs": [
{
"internalType": "enum Governance.ProposalState",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "torn",
"outputs": [
{ "internalType": "contract TORN", "name": "", "type": "address" }
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "undelegate",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "uint256", "name": "amount", "type": "uint256" }
],
"name": "unlock",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "userVault",
"outputs": [
{
"internalType": "contract ITornadoVault",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "version",
"outputs": [{ "internalType": "string", "name": "", "type": "string" }],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{ "internalType": "uint256", "name": "amount", "type": "uint256" }
],
"name": "withdrawFromHelper",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{ "stateMutability": "payable", "type": "receive" }
]

@ -0,0 +1 @@
[{"inputs":[{"internalType":"address","name":"governanceAddress","type":"address"},{"internalType":"address","name":"tornAddress","type":"address"},{"internalType":"address","name":"_relayerRegistry","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"rewardsClaimed","type":"uint256"}],"name":"RewardsClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"rewards","type":"uint256"}],"name":"RewardsUpdated","type":"event"},{"inputs":[],"name":"Governance","outputs":[{"internalType":"contract ITornadoGovernance","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"accumulatedRewardPerTorn","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"accumulatedRewardRateOnLastUpdate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"accumulatedRewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"addBurnRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32[]","name":"domains","type":"bytes32[]"}],"name":"bulkResolve","outputs":[{"internalType":"address[]","name":"result","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"checkReward","outputs":[{"internalType":"uint256","name":"rewards","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"ratioConstant","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"relayerRegistry","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"resolve","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"setReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"torn","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amountLockedBeforehand","type":"uint256"}],"name":"updateRewardsOnLockedBalanceChange","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawTorn","outputs":[],"stateMutability":"nonpayable","type":"function"}]

370
test/abi/torn.abi.json Normal file

@ -0,0 +1,370 @@
[
{
"inputs": [
{ "internalType": "bytes32", "name": "_governance", "type": "bytes32" },
{ "internalType": "uint256", "name": "_pausePeriod", "type": "uint256" },
{
"components": [
{ "internalType": "bytes32", "name": "to", "type": "bytes32" },
{ "internalType": "uint256", "name": "amount", "type": "uint256" }
],
"internalType": "struct TORN.Recipient[]",
"name": "_vestings",
"type": "tuple[]"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "target",
"type": "address"
}
],
"name": "Allowed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "target",
"type": "address"
}
],
"name": "Disallowed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "Paused",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "Unpaused",
"type": "event"
},
{
"inputs": [
{ "internalType": "address[]", "name": "target", "type": "address[]" }
],
"name": "addToAllowedList",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "owner", "type": "address" },
{ "internalType": "address", "name": "spender", "type": "address" }
],
"name": "allowance",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{ "internalType": "address", "name": "", "type": "address" }],
"name": "allowedTransferee",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "spender", "type": "address" },
{ "internalType": "uint256", "name": "amount", "type": "uint256" }
],
"name": "approve",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "account", "type": "address" }
],
"name": "balanceOf",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "blockTimestamp",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32[]", "name": "domains", "type": "bytes32[]" }
],
"name": "bulkResolve",
"outputs": [
{ "internalType": "address[]", "name": "result", "type": "address[]" }
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "uint256", "name": "amount", "type": "uint256" }
],
"name": "burn",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "account", "type": "address" },
{ "internalType": "uint256", "name": "amount", "type": "uint256" }
],
"name": "burnFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "canUnpauseAfter",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "chainID",
"outputs": [
{ "internalType": "uint256", "name": "_chainID", "type": "uint256" }
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{ "internalType": "bool", "name": "decision", "type": "bool" }],
"name": "changeTransferability",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "decimals",
"outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "spender", "type": "address" },
{
"internalType": "uint256",
"name": "subtractedValue",
"type": "uint256"
}
],
"name": "decreaseAllowance",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "governance",
"outputs": [{ "internalType": "address", "name": "", "type": "address" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "spender", "type": "address" },
{ "internalType": "uint256", "name": "addedValue", "type": "uint256" }
],
"name": "increaseAllowance",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "name",
"outputs": [{ "internalType": "string", "name": "", "type": "string" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "owner", "type": "address" }
],
"name": "nonces",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "paused",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "owner", "type": "address" },
{ "internalType": "address", "name": "spender", "type": "address" },
{ "internalType": "uint256", "name": "amount", "type": "uint256" },
{ "internalType": "uint256", "name": "deadline", "type": "uint256" },
{ "internalType": "uint8", "name": "v", "type": "uint8" },
{ "internalType": "bytes32", "name": "r", "type": "bytes32" },
{ "internalType": "bytes32", "name": "s", "type": "bytes32" }
],
"name": "permit",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "address[]", "name": "target", "type": "address[]" }
],
"name": "removeFromAllowedList",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract IERC20",
"name": "_token",
"type": "address"
},
{ "internalType": "address payable", "name": "_to", "type": "address" },
{ "internalType": "uint256", "name": "_balance", "type": "uint256" }
],
"name": "rescueTokens",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "node", "type": "bytes32" }
],
"name": "resolve",
"outputs": [{ "internalType": "address", "name": "", "type": "address" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "symbol",
"outputs": [{ "internalType": "string", "name": "", "type": "string" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalSupply",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "recipient", "type": "address" },
{ "internalType": "uint256", "name": "amount", "type": "uint256" }
],
"name": "transfer",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "sender", "type": "address" },
{ "internalType": "address", "name": "recipient", "type": "address" },
{ "internalType": "uint256", "name": "amount", "type": "uint256" }
],
"name": "transferFrom",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "nonpayable",
"type": "function"
}
]

179
test/utils.js Normal file

@ -0,0 +1,179 @@
const { ethers, network } = require("hardhat");
const { time } = require("@nomicfoundation/hardhat-toolbox/network-helpers");
const stakingAddr = "0x5B3f656C80E8ddb9ec01Dd9018815576E9238c29";
const gasCompensationAddr = "0xFA4C1f3f7D5dd7c12a9Adb82Cd7dDA542E3d59ef";
const userVaultAddr = "0x2F50508a8a3D323B91336FA3eA6ae50E55f32185";
const governanceAddr = "0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce";
const tornAddr = "0x77777FeDdddFfC19Ff86DB637967013e6C6A116C";
async function getManyEth(addr) {
await network.provider.send("hardhat_setBalance", [addr, "0x111166630153555558483537"]);
}
async function getPermitSignature(signer, tokenContract, spender, value, deadline) {
const [nonce, name, version, chainId] = await Promise.all([
tokenContract.nonces(signer.address),
tokenContract.name(),
"1",
tokenContract.chainID(),
]);
const domain = {
name,
version,
chainId,
verifyingContract: tornAddr,
};
const types = {
Permit: [
{
name: "owner",
type: "address",
},
{
name: "spender",
type: "address",
},
{
name: "value",
type: "uint256",
},
{
name: "nonce",
type: "uint256",
},
{
name: "deadline",
type: "uint256",
},
],
};
const values = {
owner: signer.address,
spender,
value,
nonce,
deadline,
};
const signature = await signer.signTypedData(domain, types, values);
return ethers.Signature.from(signature);
}
async function resetStateBeforeProposal() {
await network.provider.request({
method: "hardhat_reset",
params: [
{
forking: {
jsonRpcUrl: config.networks.hardhat.forking.url,
blockNumber: config.networks.hardhat.forking.blockNumber,
},
},
],
});
}
async function getTorn(signer) {
return await ethers.getContractAt(require("./abi/torn.abi.json"), tornAddr, signer);
}
async function getEnsRegistry() {
const ensAddr = "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e";
return await ethers.getContractAt(require("./abi/ensRegistry.abi.json"), ensAddr);
}
async function getEnsResolver(ensName) {
const ensRegistry = await getEnsRegistry();
const resolverAddr = await ensRegistry.resolver(ethers.namehash(ensName));
return await ethers.getContractAt(require("./abi/ensResolver.abi.json"), resolverAddr);
}
async function resolveAddr(ensName) {
if (ethers.isAddress(ensName)) return ensName;
const ensResolver = await getEnsResolver(ensName);
return await ensResolver.addr(ethers.namehash(ensName));
}
async function getProxyImplAddr(proxyAddr) {
const implementation = await ethers.provider.getStorage(
proxyAddr,
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
);
const [implementationAddr] = new ethers.AbiCoder().decode(["address"], implementation);
return implementationAddr;
}
async function getGovernance(signer) {
return await ethers.getContractAt(require("./abi/governance.abi.json"), governanceAddr, signer);
}
async function signerLockInGov(tornAmount, signer) {
if (!signer) signer = (await ethers.getSigners())[0];
if (tornAmount == "quorum") tornAmount = await (await getGovernance()).QUORUM_VOTES();
const governanceSigner = await ethers.getImpersonatedSigner(governanceAddr);
const torn = await getTorn(governanceSigner);
await torn.transfer(signer.address, tornAmount);
await getManyEth(signer.address);
const governance = await getGovernance(signer);
const deadline = ethers.MaxUint256;
const { v, r, s } = await getPermitSignature(signer, torn, governanceAddr, tornAmount, deadline);
await governance.lock(signer.address, tornAmount, deadline, v, r, s);
return signer;
}
async function executeNewProposal(proposalAddr) {
const governance = await getGovernance();
const quorum = await governance.QUORUM_VOTES();
const stakerSigner = await signerLockInGov(quorum);
const governanceContract = await getGovernance(stakerSigner);
await governanceContract.propose(proposalAddr, "");
const proposalId = await governanceContract.proposalCount();
await time.increase(60 * 60);
await governanceContract.castVote(proposalId, true);
await time.increase(60 * 60 * 24 * 7 + 60);
await governanceContract.execute(proposalId);
await time.increase(60 * 60 * 24 * 4);
await governanceContract.unlock(quorum);
const torn = await getTorn(stakerSigner);
await torn.transfer(governanceAddr, quorum);
}
async function deployAndExecuteProposal() {
const governanceFactory = await ethers.getContractFactory("GovernanceProposalStateUpgrade");
const governanceImpl = await governanceFactory.deploy(stakingAddr, gasCompensationAddr, userVaultAddr);
const newGovernanceImplAddr = await governanceImpl.getAddress();
const proposalFactory = await ethers.getContractFactory("Proposal");
const proposal = await proposalFactory.deploy(newGovernanceImplAddr);
const deployedProposalAddr = await proposal.getAddress();
await executeNewProposal(deployedProposalAddr);
const newGovernance = governanceFactory.attach(governanceAddr);
return { newGovernanceImplAddr, governanceContract: newGovernance };
}
module.exports = {
resetStateBeforeProposal,
deployAndExecuteProposal,
getGovernance,
getEnsRegistry,
getEnsResolver,
resolveAddr,
getProxyImplAddr,
governanceAddr,
getManyEth,
tornAddr,
getTorn,
stakingAddr,
getPermitSignature,
signerLockInGov,
executeNewProposal,
};