Redeploy staking as proxy & format all with new foundry settings
This commit is contained in:
parent
dc1bf7e2a7
commit
af38a8dcc9
@ -4,9 +4,11 @@
|
||||
"prettier/prettier": [
|
||||
"error",
|
||||
{
|
||||
"printWidth": 110
|
||||
"printWidth": 110,
|
||||
"bracketSpacing": true
|
||||
}
|
||||
],
|
||||
"no-console": "off",
|
||||
"quotes": ["error", "double"],
|
||||
"indent": ["error", 2],
|
||||
"compiler-version": ["error", "^0.6.0"]
|
||||
|
@ -5,4 +5,11 @@ out = 'out'
|
||||
libs = ["node_modules", "lib"]
|
||||
chain_id = 1
|
||||
optimizer = true
|
||||
optimizer-runs = 10_000_000
|
||||
optimizer-runs = 10_000_000
|
||||
|
||||
[fmt]
|
||||
|
||||
line_length = 140
|
||||
bracket_spacing = true
|
||||
multiline_func_header = 'attributes_first'
|
||||
number_underscore = 'thousands'
|
@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "proposal-22-forge-tests",
|
||||
"version": "1.0.0",
|
||||
"repository": "https://git.tornado.ws/AlienTornadosaurusHex/proposal-22-forge-tests.git",
|
||||
"author": "AlienTornadosaurusHex",
|
||||
"repository": "https://git.tornado.ws/Theo/proposal-22-forge-tests.git",
|
||||
"author": "Theo",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"test": "forge test -vvv --fork-url https://rpc.mevblocker.io --fork-block-number 17336117 --gas-report"
|
||||
|
@ -1,17 +1,17 @@
|
||||
pragma solidity ^0.6.12;
|
||||
|
||||
interface IGovernance {
|
||||
function lockedBalance(address account) external view returns (uint256);
|
||||
|
||||
function propose(address target, string memory description) external returns (uint256);
|
||||
|
||||
function castVote(uint256 proposalId, bool support) external;
|
||||
|
||||
function lock(address owner, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external;
|
||||
|
||||
function lockWithApproval(uint256 amount) external;
|
||||
|
||||
function unlock(uint256 amount) external;
|
||||
|
||||
function execute(uint256 proposalId) external payable;
|
||||
}
|
||||
pragma solidity ^0.6.12;
|
||||
|
||||
interface IGovernance {
|
||||
function lockedBalance(address account) external view returns (uint256);
|
||||
|
||||
function propose(address target, string memory description) external returns (uint256);
|
||||
|
||||
function castVote(uint256 proposalId, bool support) external;
|
||||
|
||||
function lock(address owner, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external;
|
||||
|
||||
function lockWithApproval(uint256 amount) external;
|
||||
|
||||
function unlock(uint256 amount) external;
|
||||
|
||||
function execute(uint256 proposalId) external payable;
|
||||
}
|
||||
|
@ -3,71 +3,71 @@
|
||||
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;
|
||||
/// @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");
|
||||
_;
|
||||
}
|
||||
modifier onlySelf() {
|
||||
require(msg.sender == address(this), "Governance: unauthorized");
|
||||
_;
|
||||
}
|
||||
|
||||
function _initializeConfiguration() internal {
|
||||
EXECUTION_DELAY = 2 days;
|
||||
EXECUTION_EXPIRATION = 3 days;
|
||||
QUORUM_VOTES = 25000e18; // 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 _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 setExecutionDelay(uint256 executionDelay) external onlySelf {
|
||||
EXECUTION_DELAY = executionDelay;
|
||||
}
|
||||
|
||||
function setExecutionExpiration(uint256 executionExpiration) external onlySelf {
|
||||
EXECUTION_EXPIRATION = executionExpiration;
|
||||
}
|
||||
function setExecutionExpiration(uint256 executionExpiration) external onlySelf {
|
||||
EXECUTION_EXPIRATION = executionExpiration;
|
||||
}
|
||||
|
||||
function setQuorumVotes(uint256 quorumVotes) external onlySelf {
|
||||
QUORUM_VOTES = quorumVotes;
|
||||
}
|
||||
function setQuorumVotes(uint256 quorumVotes) external onlySelf {
|
||||
QUORUM_VOTES = quorumVotes;
|
||||
}
|
||||
|
||||
function setProposalThreshold(uint256 proposalThreshold) external onlySelf {
|
||||
PROPOSAL_THRESHOLD = proposalThreshold;
|
||||
}
|
||||
function setProposalThreshold(uint256 proposalThreshold) external onlySelf {
|
||||
PROPOSAL_THRESHOLD = proposalThreshold;
|
||||
}
|
||||
|
||||
function setVotingDelay(uint256 votingDelay) external onlySelf {
|
||||
VOTING_DELAY = votingDelay;
|
||||
}
|
||||
function setVotingDelay(uint256 votingDelay) external onlySelf {
|
||||
VOTING_DELAY = votingDelay;
|
||||
}
|
||||
|
||||
function setVotingPeriod(uint256 votingPeriod) external onlySelf {
|
||||
VOTING_PERIOD = votingPeriod;
|
||||
}
|
||||
function setVotingPeriod(uint256 votingPeriod) external onlySelf {
|
||||
VOTING_PERIOD = votingPeriod;
|
||||
}
|
||||
|
||||
function setClosingPeriod(uint256 closingPeriod) external onlySelf {
|
||||
CLOSING_PERIOD = closingPeriod;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,6 @@ pragma solidity ^0.6.0;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
abstract contract Core {
|
||||
/// @notice Locked token balance for each account
|
||||
mapping(address => uint256) public lockedBalance;
|
||||
/// @notice Locked token balance for each account
|
||||
mapping(address => uint256) public lockedBalance;
|
||||
}
|
||||
|
@ -5,62 +5,46 @@ pragma solidity ^0.6.0;
|
||||
import "./Core.sol";
|
||||
|
||||
abstract contract Delegation is Core {
|
||||
/// @notice Delegatee records
|
||||
mapping(address => address) public delegatedTo;
|
||||
/// @notice Delegatee records
|
||||
mapping(address => address) public delegatedTo;
|
||||
|
||||
event Delegated(address indexed account, address indexed to);
|
||||
event Undelegated(address indexed account, address indexed from);
|
||||
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);
|
||||
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);
|
||||
}
|
||||
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");
|
||||
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);
|
||||
delegatedTo[msg.sender] = address(0);
|
||||
emit Undelegated(msg.sender, previous);
|
||||
}
|
||||
if (lockedBalance[msg.sender] > 0) {
|
||||
_castVote(msg.sender, proposalId, support);
|
||||
}
|
||||
}
|
||||
|
||||
function _castVote(
|
||||
address voter,
|
||||
uint256 proposalId,
|
||||
bool support
|
||||
) internal virtual;
|
||||
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;
|
||||
}
|
||||
|
@ -12,279 +12,265 @@ 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
|
||||
}
|
||||
using SafeMath for uint256;
|
||||
/// @notice Possible states that a proposal may be in
|
||||
|
||||
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;
|
||||
}
|
||||
enum ProposalState {
|
||||
Pending,
|
||||
Active,
|
||||
Defeated,
|
||||
Timelocked,
|
||||
AwaitingExecution,
|
||||
Executed,
|
||||
Expired
|
||||
}
|
||||
|
||||
/// @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;
|
||||
}
|
||||
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 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;
|
||||
/// @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;
|
||||
}
|
||||
|
||||
TORN public torn;
|
||||
/// @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;
|
||||
|
||||
/// @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
|
||||
);
|
||||
TORN public torn;
|
||||
|
||||
/// @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
|
||||
})
|
||||
/// @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
|
||||
);
|
||||
_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);
|
||||
}
|
||||
/// @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);
|
||||
|
||||
function lockWithApproval(uint256 amount) public virtual {
|
||||
_transferTokens(msg.sender, amount);
|
||||
}
|
||||
/// @notice An event emitted when a proposal has been executed
|
||||
event ProposalExecuted(uint256 indexed proposalId);
|
||||
|
||||
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"
|
||||
);
|
||||
/// @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();
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
if (support) {
|
||||
proposal.forVotes = proposal.forVotes.add(votes);
|
||||
} else {
|
||||
proposal.againstVotes = proposal.againstVotes.add(votes);
|
||||
function lockWithApproval(uint256 amount) public virtual {
|
||||
_transferTokens(msg.sender, amount);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
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 propose(address target, string memory description) external returns (uint256) {
|
||||
return _propose(msg.sender, target, description);
|
||||
}
|
||||
}
|
||||
|
||||
function _transferTokens(address owner, uint256 amount) internal virtual {
|
||||
require(torn.transferFrom(owner, address(this), amount), "TORN: transferFrom failed");
|
||||
lockedBalance[owner] = lockedBalance[owner].add(amount);
|
||||
}
|
||||
/**
|
||||
* @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");
|
||||
|
||||
function getReceipt(uint256 proposalId, address voter) public view returns (Receipt memory) {
|
||||
return proposals[proposalId].receipts[voter];
|
||||
}
|
||||
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"
|
||||
);
|
||||
}
|
||||
|
||||
function state(uint256 proposalId) public view 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;
|
||||
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 proposalCount() public view returns (uint256) {
|
||||
return proposals.length - 1;
|
||||
}
|
||||
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;
|
||||
|
||||
function getBlockTimestamp() internal view virtual returns (uint256) {
|
||||
// solium-disable-next-line security/no-block-members
|
||||
return block.timestamp;
|
||||
}
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
|
@ -10,13 +10,13 @@ import "torn-token/contracts/ENS.sol";
|
||||
* 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 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 {}
|
||||
/**
|
||||
* @dev Override to allow admin (itself) access the fallback function.
|
||||
*/
|
||||
function _beforeFallback() internal override { }
|
||||
}
|
||||
|
@ -3,30 +3,30 @@
|
||||
pragma solidity ^0.6.0;
|
||||
|
||||
contract Dummy {
|
||||
uint256 public value;
|
||||
string public text;
|
||||
uint256 public value;
|
||||
string public text;
|
||||
|
||||
function initialize() public {
|
||||
value = 1;
|
||||
text = "dummy";
|
||||
}
|
||||
function initialize() public {
|
||||
value = 1;
|
||||
text = "dummy";
|
||||
}
|
||||
|
||||
// function update(address _impl) public {
|
||||
// MyProxy(address(uint160(address(this)))).upgradeTo(_impl);
|
||||
// // MyProxy(address(this)).upgradeTo(_impl);
|
||||
// }
|
||||
// function update(address _impl) public {
|
||||
// MyProxy(address(uint160(address(this)))).upgradeTo(_impl);
|
||||
// // MyProxy(address(this)).upgradeTo(_impl);
|
||||
// }
|
||||
}
|
||||
|
||||
contract DummySecond {
|
||||
uint256 public value;
|
||||
string public text;
|
||||
uint256 public value;
|
||||
string public text;
|
||||
|
||||
function initialize() public {
|
||||
value = 2;
|
||||
text = "dummy2";
|
||||
}
|
||||
function initialize() public {
|
||||
value = 2;
|
||||
text = "dummy2";
|
||||
}
|
||||
|
||||
// function update(address _impl) public {
|
||||
// MyProxy(address(uint160(address(this)))).upgradeTo(_impl);
|
||||
// }
|
||||
// function update(address _impl) public {
|
||||
// MyProxy(address(uint160(address(this)))).upgradeTo(_impl);
|
||||
// }
|
||||
}
|
||||
|
@ -6,22 +6,22 @@ pragma experimental ABIEncoderV2;
|
||||
import "../Governance.sol";
|
||||
|
||||
contract MockGovernance is Governance {
|
||||
uint256 public time = block.timestamp;
|
||||
uint256 public time = block.timestamp;
|
||||
|
||||
function setTimestamp(uint256 time_) public {
|
||||
time = time_;
|
||||
}
|
||||
function setTimestamp(uint256 time_) public {
|
||||
time = time_;
|
||||
}
|
||||
|
||||
function getBlockTimestamp() internal view override returns (uint256) {
|
||||
// solium-disable-next-line security/no-block-members
|
||||
return time;
|
||||
}
|
||||
function getBlockTimestamp() internal view override returns (uint256) {
|
||||
// solium-disable-next-line security/no-block-members
|
||||
return time;
|
||||
}
|
||||
|
||||
function setTorn(address torna) external {
|
||||
torn = TORN(torna);
|
||||
}
|
||||
function setTorn(address torna) external {
|
||||
torn = TORN(torna);
|
||||
}
|
||||
|
||||
function resolve(bytes32 addr) public view override returns (address) {
|
||||
return address(uint160(uint256(addr) >> (12 * 8)));
|
||||
}
|
||||
function resolve(bytes32 addr) public view override returns (address) {
|
||||
return address(uint160(uint256(addr) >> (12 * 8)));
|
||||
}
|
||||
}
|
||||
|
@ -5,9 +5,9 @@ pragma solidity ^0.6.0;
|
||||
import "../LoopbackProxy.sol";
|
||||
|
||||
contract MockProxy is LoopbackProxy {
|
||||
constructor(address _logic, bytes memory _data) public payable LoopbackProxy(_logic, _data) {}
|
||||
constructor(address _logic, bytes memory _data) public payable LoopbackProxy(_logic, _data) { }
|
||||
|
||||
function resolve(bytes32 addr) public view override returns (address) {
|
||||
return address(uint160(uint256(addr) >> (12 * 8)));
|
||||
}
|
||||
function resolve(bytes32 addr) public view override returns (address) {
|
||||
return address(uint160(uint256(addr) >> (12 * 8)));
|
||||
}
|
||||
}
|
||||
|
@ -5,15 +5,15 @@ pragma solidity ^0.6.0;
|
||||
import "./Dummy.sol";
|
||||
|
||||
contract Proposal {
|
||||
// bytes32 public constant WEIRD = keccak256("Hey Proposal");
|
||||
// uint256 public someValue = 111;
|
||||
// Dummy public dummyInstance;
|
||||
event Debug(address output);
|
||||
// bytes32 public constant WEIRD = keccak256("Hey Proposal");
|
||||
// uint256 public someValue = 111;
|
||||
// Dummy public dummyInstance;
|
||||
event Debug(address output);
|
||||
|
||||
function executeProposal() public {
|
||||
// someValue = 321;
|
||||
Dummy dummyInstance = new Dummy();
|
||||
dummyInstance.initialize();
|
||||
emit Debug(address(dummyInstance));
|
||||
}
|
||||
function executeProposal() public {
|
||||
// someValue = 321;
|
||||
Dummy dummyInstance = new Dummy();
|
||||
dummyInstance.initialize();
|
||||
emit Debug(address(dummyInstance));
|
||||
}
|
||||
}
|
||||
|
@ -3,11 +3,11 @@
|
||||
pragma solidity ^0.6.0;
|
||||
|
||||
interface IGovernance {
|
||||
function setExecutionDelay(uint256 delay) external;
|
||||
function setExecutionDelay(uint256 delay) external;
|
||||
}
|
||||
|
||||
contract ProposalStateChangeGovernance {
|
||||
function executeProposal() public {
|
||||
IGovernance(address(this)).setExecutionDelay(3 days);
|
||||
}
|
||||
function executeProposal() public {
|
||||
IGovernance(address(this)).setExecutionDelay(3 days);
|
||||
}
|
||||
}
|
||||
|
@ -6,27 +6,28 @@ pragma experimental ABIEncoderV2;
|
||||
import "./MockGovernance.sol";
|
||||
|
||||
interface IProxy {
|
||||
function upgradeTo(address newImplementation) external;
|
||||
function upgradeTo(address newImplementation) external;
|
||||
}
|
||||
|
||||
contract NewImplementation is MockGovernance {
|
||||
uint256 public newVariable;
|
||||
event Overriden(uint256 x);
|
||||
uint256 public newVariable;
|
||||
|
||||
function execute(uint256 proposalId) public payable override {
|
||||
newVariable = 999;
|
||||
emit Overriden(proposalId);
|
||||
}
|
||||
event Overriden(uint256 x);
|
||||
|
||||
function execute(uint256 proposalId) public payable override {
|
||||
newVariable = 999;
|
||||
emit Overriden(proposalId);
|
||||
}
|
||||
}
|
||||
|
||||
contract ProposalUpgrade {
|
||||
address public immutable newLogic;
|
||||
address public immutable newLogic;
|
||||
|
||||
constructor(address _newLogic) public {
|
||||
newLogic = _newLogic;
|
||||
}
|
||||
constructor(address _newLogic) public {
|
||||
newLogic = _newLogic;
|
||||
}
|
||||
|
||||
function executeProposal() public {
|
||||
IProxy(address(this)).upgradeTo(newLogic);
|
||||
}
|
||||
function executeProposal() public {
|
||||
IProxy(address(this)).upgradeTo(newLogic);
|
||||
}
|
||||
}
|
||||
|
@ -6,27 +6,26 @@ pragma experimental ABIEncoderV2;
|
||||
import "torn-token/contracts/mocks/TORNMock.sol";
|
||||
|
||||
struct Recipient2 {
|
||||
address to;
|
||||
uint256 amount;
|
||||
address to;
|
||||
uint256 amount;
|
||||
}
|
||||
|
||||
contract TORNMock2 is TORNMock {
|
||||
constructor(
|
||||
address _governance,
|
||||
uint256 _pausePeriod,
|
||||
Recipient2[] memory vesting
|
||||
) public TORNMock(solve(_governance), _pausePeriod, solve2(vesting)) {}
|
||||
constructor(address _governance, uint256 _pausePeriod, Recipient2[] memory vesting)
|
||||
public
|
||||
TORNMock(solve(_governance), _pausePeriod, solve2(vesting))
|
||||
{ }
|
||||
|
||||
function solve(address x) private returns (bytes32) {
|
||||
return bytes32(uint256(x) << 96);
|
||||
}
|
||||
|
||||
function solve2(Recipient2[] memory vesting) private returns (Recipient[] memory) {
|
||||
Recipient[] memory realVesting = new Recipient[](vesting.length);
|
||||
for (uint256 i = 0; i < vesting.length; i++) {
|
||||
realVesting[i].to = solve(vesting[i].to);
|
||||
realVesting[i].amount = vesting[i].amount;
|
||||
function solve(address x) private returns (bytes32) {
|
||||
return bytes32(uint256(x) << 96);
|
||||
}
|
||||
|
||||
function solve2(Recipient2[] memory vesting) private returns (Recipient[] memory) {
|
||||
Recipient[] memory realVesting = new Recipient[](vesting.length);
|
||||
for (uint256 i = 0; i < vesting.length; i++) {
|
||||
realVesting[i].to = solve(vesting[i].to);
|
||||
realVesting[i].amount = vesting[i].amount;
|
||||
}
|
||||
return realVesting;
|
||||
}
|
||||
return realVesting;
|
||||
}
|
||||
}
|
||||
|
@ -5,54 +5,54 @@ pragma solidity ^0.6.12;
|
||||
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
|
||||
|
||||
interface IGasCompensationVault {
|
||||
function compensateGas(address recipient, uint256 gasAmount) external;
|
||||
function compensateGas(address recipient, uint256 gasAmount) external;
|
||||
|
||||
function withdrawToGovernance(uint256 amount) 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;
|
||||
using SafeMath for uint256;
|
||||
|
||||
/// @notice this vault is necessary for the gas compensation functionality to work
|
||||
IGasCompensationVault public immutable gasCompensationVault;
|
||||
/// @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 {
|
||||
_;
|
||||
constructor(address _gasCompensationVault) public {
|
||||
gasCompensationVault = IGasCompensationVault(_gasCompensationVault);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice inheritable unimplemented function to withdraw ether from the vault
|
||||
* */
|
||||
function withdrawFromHelper(uint256 amount) external virtual;
|
||||
/**
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* @notice inheritable unimplemented function to deposit ether into the vault
|
||||
* */
|
||||
function setGasCompensations(uint256 _gasCompensationsLimit) external virtual;
|
||||
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;
|
||||
}
|
||||
|
@ -10,146 +10,147 @@ 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 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);
|
||||
/// @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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,35 +9,35 @@ import { ITornadoVault } from "./interfaces/ITornadoVault.sol";
|
||||
|
||||
/// @title Version 2 Governance contract of the tornado.cash governance
|
||||
contract GovernanceVaultUpgrade is Governance {
|
||||
using SafeMath for uint256;
|
||||
using SafeMath for uint256;
|
||||
|
||||
// vault which stores user TORN
|
||||
ITornadoVault public immutable userVault;
|
||||
// vault which stores user TORN
|
||||
ITornadoVault public immutable userVault;
|
||||
|
||||
// call Governance v1 constructor
|
||||
constructor(address _userVault) public Governance() {
|
||||
userVault = ITornadoVault(_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 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 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);
|
||||
}
|
||||
/// @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);
|
||||
}
|
||||
}
|
||||
|
@ -4,5 +4,5 @@ pragma solidity ^0.6.12;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
interface ITornadoVault {
|
||||
function withdrawTorn(address recipient, uint256 amount) external;
|
||||
function withdrawTorn(address recipient, uint256 amount) external;
|
||||
}
|
||||
|
@ -5,12 +5,12 @@ pragma solidity ^0.6.12;
|
||||
import "../../v1/Governance.sol";
|
||||
|
||||
contract MockProposal {
|
||||
address public constant GovernanceAddress = 0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce;
|
||||
address public constant GovernanceAddress = 0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce;
|
||||
|
||||
function executeProposal() external {
|
||||
Governance gov = Governance(GovernanceAddress);
|
||||
function executeProposal() external {
|
||||
Governance gov = Governance(GovernanceAddress);
|
||||
|
||||
gov.setVotingPeriod(27000);
|
||||
require(gov.VOTING_PERIOD() == 27000, "Voting period change failed!");
|
||||
}
|
||||
gov.setVotingPeriod(27_000);
|
||||
require(gov.VOTING_PERIOD() == 27_000, "Voting period change failed!");
|
||||
}
|
||||
}
|
||||
|
@ -13,49 +13,47 @@ import { ITornadoStakingRewards } from "./interfaces/ITornadoStakingRewards.sol"
|
||||
* - generally inherits risks from former governance upgrades
|
||||
*/
|
||||
contract GovernanceStakingUpgrade is GovernanceGasUpgrade {
|
||||
ITornadoStakingRewards public immutable Staking;
|
||||
ITornadoStakingRewards public immutable Staking;
|
||||
|
||||
event RewardUpdateSuccessful(address indexed account);
|
||||
event RewardUpdateFailed(address indexed account, bytes indexed errorData);
|
||||
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);
|
||||
constructor(address stakingRewardsAddress, address gasCompLogic, address userVaultAddress)
|
||||
public
|
||||
GovernanceGasUpgrade(gasCompLogic, userVaultAddress)
|
||||
{
|
||||
Staking = ITornadoStakingRewards(stakingRewardsAddress);
|
||||
}
|
||||
_;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
/**
|
||||
* @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 lockWithApproval(uint256 amount) public virtual override updateRewards(msg.sender) {
|
||||
super.lockWithApproval(amount);
|
||||
}
|
||||
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 unlock(uint256 amount) public virtual override updateRewards(msg.sender) {
|
||||
super.unlock(amount);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -3,5 +3,5 @@
|
||||
pragma solidity ^0.6.12;
|
||||
|
||||
interface ITornadoStakingRewards {
|
||||
function updateRewardsOnLockedBalanceChange(address account, uint256 amountLockedBeforehand) external;
|
||||
function updateRewardsOnLockedBalanceChange(address account, uint256 amountLockedBeforehand) external;
|
||||
}
|
||||
|
20
src/v4-patch/AdminUpgradeableProxy.sol
Normal file
20
src/v4-patch/AdminUpgradeableProxy.sol
Normal file
@ -0,0 +1,20 @@
|
||||
// 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 { }
|
||||
}
|
@ -7,64 +7,64 @@ import "../v1/Governance.sol";
|
||||
import "../v3-relayer-registry/GovernanceStakingUpgrade.sol";
|
||||
|
||||
contract GovernancePatchUpgrade is GovernanceStakingUpgrade {
|
||||
mapping(uint256 => bytes32) public proposalCodehashes;
|
||||
mapping(uint256 => bytes32) public proposalCodehashes;
|
||||
|
||||
constructor(
|
||||
address stakingRewardsAddress,
|
||||
address gasCompLogic,
|
||||
address userVaultAddress
|
||||
) public GovernanceStakingUpgrade(stakingRewardsAddress, gasCompLogic, userVaultAddress) {}
|
||||
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)
|
||||
/// @notice Return the version of the contract
|
||||
function version() external pure virtual override returns (string memory) {
|
||||
return "4.patch-exploit";
|
||||
}
|
||||
|
||||
require(proposalCodehash == proposalCodehashes[proposalId], "Governance::propose: metamorphic contracts not allowed");
|
||||
/**
|
||||
* @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");
|
||||
|
||||
super.execute(proposalId);
|
||||
}
|
||||
Proposal storage proposal = proposals[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);
|
||||
address target = proposal.target;
|
||||
|
||||
bytes32 proposalCodehash;
|
||||
bytes32 proposalCodehash;
|
||||
|
||||
assembly {
|
||||
proposalCodehash := extcodehash(target)
|
||||
assembly {
|
||||
proposalCodehash := extcodehash(target)
|
||||
}
|
||||
|
||||
require(proposalCodehash == proposalCodehashes[proposalId], "Governance::propose: metamorphic contracts not allowed");
|
||||
|
||||
super.execute(proposalId);
|
||||
}
|
||||
|
||||
proposalCodehashes[proposalId] = proposalCodehash;
|
||||
}
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ pragma experimental ABIEncoderV2;
|
||||
|
||||
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
|
||||
import { LoopbackProxy } from "../v1/LoopbackProxy.sol";
|
||||
import { AdminUpgradeableProxy } from "./AdminUpgradeableProxy.sol";
|
||||
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
|
||||
|
||||
@ -12,100 +13,88 @@ import { GovernancePatchUpgrade } from "./GovernancePatchUpgrade.sol";
|
||||
import { TornadoStakingRewards } from "./TornadoStakingRewards.sol";
|
||||
import { RelayerRegistry } from "./RelayerRegistry.sol";
|
||||
|
||||
interface Proxy {
|
||||
function upgradeTo(address newImplementation) external;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Contract which should help the proposal deploy the necessary contracts.
|
||||
*/
|
||||
contract PatchProposalContractsFactory {
|
||||
/**
|
||||
* @notice Create a new TornadoStakingRewards contract.
|
||||
* @param governance The address of Tornado Cash Goveranance.
|
||||
* @param torn The torn token address.
|
||||
* @param registry The address of the relayer registry.
|
||||
* @return The address of the new staking contract.
|
||||
*/
|
||||
function createStakingRewards(
|
||||
address governance,
|
||||
address torn,
|
||||
address registry
|
||||
) external returns (address) {
|
||||
return address(new TornadoStakingRewards(governance, torn, registry));
|
||||
}
|
||||
/**
|
||||
* @notice Create a new TornadoStakingRewards contract.
|
||||
* @param governance The address of Tornado Cash Goveranance.
|
||||
* @param torn The torn token address.
|
||||
* @param registry The address of the relayer registry.
|
||||
* @return The address of the new staking contract.
|
||||
*/
|
||||
function createStakingRewards(address governance, address torn, address registry) external returns (address) {
|
||||
return address(new TornadoStakingRewards(governance, torn, registry));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Create a new RelayerRegistry contract.
|
||||
* @param torn The torn token address.
|
||||
* @param governance The address of Tornado Cash Goveranance.
|
||||
* @param ens The ens registrar address.
|
||||
* @param staking The TornadoStakingRewards contract address.
|
||||
* @return The address of the new registry contract.
|
||||
*/
|
||||
function createRegistryContract(
|
||||
address torn,
|
||||
address governance,
|
||||
address ens,
|
||||
address staking,
|
||||
address feeManager
|
||||
) external returns (address) {
|
||||
return address(new RelayerRegistry(torn, governance, ens, staking, feeManager));
|
||||
}
|
||||
/**
|
||||
* @notice Create a new RelayerRegistry contract.
|
||||
* @param torn The torn token address.
|
||||
* @param governance The address of Tornado Cash Goveranance.
|
||||
* @param ens The ens registrar address.
|
||||
* @param staking The TornadoStakingRewards contract address.
|
||||
* @return The address of the new registry contract.
|
||||
*/
|
||||
function createRegistryContract(address torn, address governance, address ens, address staking, address feeManager)
|
||||
external
|
||||
returns (address)
|
||||
{
|
||||
return address(new RelayerRegistry(torn, governance, ens, staking, feeManager));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Proposal which should patch governance against the metamorphic contract replacement vulnerability.
|
||||
*/
|
||||
contract PatchProposal {
|
||||
using SafeMath for uint256;
|
||||
using Address for address;
|
||||
using SafeMath for uint256;
|
||||
using Address for address;
|
||||
|
||||
address public constant feeManagerAddress = 0x5f6c97C6AD7bdd0AE7E0Dd4ca33A4ED3fDabD4D7;
|
||||
address public constant ensAddress = 0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e;
|
||||
address public immutable registry = 0x58E8dCC13BE9780fC42E8723D8EaD4CF46943dF2;
|
||||
address public immutable feeManagerAddress = 0x5f6c97C6AD7bdd0AE7E0Dd4ca33A4ED3fDabD4D7;
|
||||
address public immutable ensAddress = 0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e;
|
||||
address public immutable registry = 0x58E8dCC13BE9780fC42E8723D8EaD4CF46943dF2;
|
||||
|
||||
IERC20 public constant TORN = IERC20(0x77777FeDdddFfC19Ff86DB637967013e6C6A116C);
|
||||
IERC20 public constant TORN = IERC20(0x77777FeDdddFfC19Ff86DB637967013e6C6A116C);
|
||||
|
||||
PatchProposalContractsFactory public immutable patchProposalContractsFactory;
|
||||
PatchProposalContractsFactory public immutable patchProposalContractsFactory;
|
||||
|
||||
constructor(address _patchProposalContractsFactory) public {
|
||||
patchProposalContractsFactory = PatchProposalContractsFactory(_patchProposalContractsFactory);
|
||||
}
|
||||
constructor(address _patchProposalContractsFactory) public {
|
||||
patchProposalContractsFactory = PatchProposalContractsFactory(_patchProposalContractsFactory);
|
||||
}
|
||||
|
||||
/// @notice Function to execute the proposal.
|
||||
function executeProposal() external {
|
||||
// address(this) has to be governance
|
||||
address payable governance = payable(address(this));
|
||||
/// @notice Function to execute the proposal.
|
||||
function executeProposal() external {
|
||||
// address(this) has to be governance
|
||||
address payable governance = payable(address(this));
|
||||
|
||||
// Get the two contracts gov depends on
|
||||
address gasComp = address(GovernancePatchUpgrade(governance).gasCompensationVault());
|
||||
address vault = address(GovernancePatchUpgrade(governance).userVault());
|
||||
// Get the two contracts gov depends on
|
||||
address gasComp = address(GovernancePatchUpgrade(governance).gasCompensationVault());
|
||||
address vault = address(GovernancePatchUpgrade(governance).userVault());
|
||||
|
||||
// Get the old staking contract
|
||||
TornadoStakingRewards oldStaking = TornadoStakingRewards(address(GovernancePatchUpgrade(governance).Staking()));
|
||||
// Get the old staking contract
|
||||
TornadoStakingRewards oldStaking = TornadoStakingRewards(address(GovernancePatchUpgrade(governance).Staking()));
|
||||
|
||||
// Get the small amount of TORN left
|
||||
oldStaking.withdrawTorn(TORN.balanceOf(address(oldStaking)));
|
||||
// Get the small amount of TORN left
|
||||
oldStaking.withdrawTorn(TORN.balanceOf(address(oldStaking)));
|
||||
|
||||
// And create a new staking contract
|
||||
TornadoStakingRewards newStaking = TornadoStakingRewards(
|
||||
patchProposalContractsFactory.createStakingRewards(address(governance), address(TORN), registry)
|
||||
);
|
||||
// And create a new staking logic contract
|
||||
TornadoStakingRewards newStakingImplementation =
|
||||
TornadoStakingRewards(patchProposalContractsFactory.createStakingRewards(address(governance), address(TORN), registry));
|
||||
|
||||
// And a new registry implementation
|
||||
address newRegistryImplementationAddress = patchProposalContractsFactory.createRegistryContract(
|
||||
address(TORN),
|
||||
address(governance),
|
||||
ensAddress,
|
||||
address(newStaking),
|
||||
feeManagerAddress
|
||||
);
|
||||
// Create new staking proxy contract (without initialization value)
|
||||
bytes memory empty;
|
||||
AdminUpgradeableProxy newStaking = new AdminUpgradeableProxy(address(newStakingImplementation), address(governance), empty);
|
||||
|
||||
// Upgrade the registry proxy
|
||||
Proxy(registry).upgradeTo(newRegistryImplementationAddress);
|
||||
// And a new registry implementation
|
||||
address newRegistryImplementationAddress = patchProposalContractsFactory.createRegistryContract(
|
||||
address(TORN), address(governance), ensAddress, address(newStaking), feeManagerAddress
|
||||
);
|
||||
|
||||
// Now upgrade the governance to the latest stuff
|
||||
LoopbackProxy(payable(governance)).upgradeTo(address(new GovernancePatchUpgrade(address(newStaking), gasComp, vault)));
|
||||
}
|
||||
// Upgrade the registry proxy
|
||||
AdminUpgradeableProxy(payable(registry)).upgradeTo(newRegistryImplementationAddress);
|
||||
|
||||
// Now upgrade the governance to the latest stuff
|
||||
LoopbackProxy(payable(governance)).upgradeTo(address(new GovernancePatchUpgrade(address(newStaking), gasComp, vault)));
|
||||
}
|
||||
}
|
||||
|
@ -12,25 +12,25 @@ import { TORN } from "torn-token/contracts/TORN.sol";
|
||||
import { TornadoStakingRewards } from "./TornadoStakingRewards.sol";
|
||||
|
||||
interface ITornadoInstance {
|
||||
function token() external view returns (address);
|
||||
function token() external view returns (address);
|
||||
|
||||
function denomination() external view returns (uint256);
|
||||
function denomination() external view returns (uint256);
|
||||
|
||||
function deposit(bytes32 commitment) external payable;
|
||||
function deposit(bytes32 commitment) external payable;
|
||||
|
||||
function withdraw(
|
||||
bytes calldata proof,
|
||||
bytes32 root,
|
||||
bytes32 nullifierHash,
|
||||
address payable recipient,
|
||||
address payable relayer,
|
||||
uint256 fee,
|
||||
uint256 refund
|
||||
) external payable;
|
||||
function withdraw(
|
||||
bytes calldata proof,
|
||||
bytes32 root,
|
||||
bytes32 nullifierHash,
|
||||
address payable recipient,
|
||||
address payable relayer,
|
||||
uint256 fee,
|
||||
uint256 refund
|
||||
) external payable;
|
||||
}
|
||||
|
||||
interface IENS {
|
||||
function owner(bytes32 node) external view returns (address);
|
||||
function owner(bytes32 node) external view returns (address);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -40,45 +40,41 @@ interface IENS {
|
||||
* 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++;
|
||||
function namehash(bytes memory domain) internal pure returns (bytes32) {
|
||||
return namehash(domain, 0);
|
||||
}
|
||||
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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface IFeeManager {
|
||||
function instanceFeeWithUpdate(ITornadoInstance _instance) external returns (uint160);
|
||||
function instanceFeeWithUpdate(ITornadoInstance _instance) external returns (uint160);
|
||||
}
|
||||
|
||||
struct RelayerState {
|
||||
uint256 balance;
|
||||
bytes32 ensHash;
|
||||
uint256 balance;
|
||||
bytes32 ensHash;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -93,292 +89,279 @@ struct RelayerState {
|
||||
* - if setter functions are compromised, relayer metadata would be at risk, including the noted amount of his balance
|
||||
* - if burn function is compromised, relayers run the risk of being unable to handle withdrawals
|
||||
* - the above risk also applies to the nullify balance function
|
||||
* */
|
||||
*
|
||||
*/
|
||||
contract RelayerRegistry is Initializable, EnsResolve {
|
||||
using SafeMath for uint256;
|
||||
using SafeERC20 for TORN;
|
||||
using ENSNamehash for bytes;
|
||||
using SafeMath for uint256;
|
||||
using SafeERC20 for TORN;
|
||||
using ENSNamehash for bytes;
|
||||
|
||||
TORN public immutable torn;
|
||||
address public immutable governance;
|
||||
IENS public immutable ens;
|
||||
TornadoStakingRewards public immutable staking;
|
||||
IFeeManager public immutable feeManager;
|
||||
TORN public immutable torn;
|
||||
address public immutable governance;
|
||||
IENS public immutable ens;
|
||||
TornadoStakingRewards public immutable staking;
|
||||
IFeeManager public immutable feeManager;
|
||||
|
||||
address public tornadoRouter;
|
||||
uint256 public minStakeAmount;
|
||||
address public tornadoRouter;
|
||||
uint256 public minStakeAmount;
|
||||
|
||||
mapping(address => RelayerState) public relayers;
|
||||
mapping(address => address) public workers;
|
||||
mapping(address => RelayerState) public relayers;
|
||||
mapping(address => address) public workers;
|
||||
|
||||
event RelayerBalanceNullified(address relayer);
|
||||
event WorkerRegistered(address relayer, address worker);
|
||||
event WorkerUnregistered(address relayer, address worker);
|
||||
event StakeAddedToRelayer(address relayer, uint256 amountStakeAdded);
|
||||
event StakeBurned(address relayer, uint256 amountBurned);
|
||||
event MinimumStakeAmount(uint256 minStakeAmount);
|
||||
event RouterRegistered(address tornadoRouter);
|
||||
event RelayerRegistered(bytes32 relayer, string ensName, address relayerAddress, uint256 stakedAmount);
|
||||
event RelayerBalanceNullified(address relayer);
|
||||
event WorkerRegistered(address relayer, address worker);
|
||||
event WorkerUnregistered(address relayer, address worker);
|
||||
event StakeAddedToRelayer(address relayer, uint256 amountStakeAdded);
|
||||
event StakeBurned(address relayer, uint256 amountBurned);
|
||||
event MinimumStakeAmount(uint256 minStakeAmount);
|
||||
event RouterRegistered(address tornadoRouter);
|
||||
event RelayerRegistered(bytes32 relayer, string ensName, address relayerAddress, uint256 stakedAmount);
|
||||
|
||||
modifier onlyGovernance() {
|
||||
require(msg.sender == governance, "only governance");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier onlyTornadoRouter() {
|
||||
require(msg.sender == tornadoRouter, "only proxy");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier onlyRelayer(address sender, address relayer) {
|
||||
require(workers[sender] == relayer, "only relayer");
|
||||
_;
|
||||
}
|
||||
|
||||
constructor(
|
||||
address _torn,
|
||||
address _governance,
|
||||
address _ens,
|
||||
address _staking,
|
||||
address _feeManager
|
||||
) public {
|
||||
torn = TORN(_torn);
|
||||
governance = _governance;
|
||||
ens = IENS(_ens);
|
||||
staking = TornadoStakingRewards(_staking);
|
||||
feeManager = IFeeManager(_feeManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice initialize function for upgradeability
|
||||
* @dev this contract will be deployed behind a proxy and should not assign values at logic address,
|
||||
* params left out because self explainable
|
||||
* */
|
||||
function initialize(bytes32 _tornadoRouter) external initializer {
|
||||
tornadoRouter = resolve(_tornadoRouter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This function should register a master address and optionally a set of workeres for a relayer + metadata
|
||||
* @dev Relayer can't steal other relayers workers since they are registered, and a wallet (msg.sender check) can always unregister itself
|
||||
* @param ensName ens name of the relayer
|
||||
* @param stake the initial amount of stake in TORN the relayer is depositing
|
||||
* */
|
||||
function register(
|
||||
string calldata ensName,
|
||||
uint256 stake,
|
||||
address[] calldata workersToRegister
|
||||
) external {
|
||||
_register(msg.sender, ensName, stake, workersToRegister);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Register function equivalent with permit-approval instead of regular approve.
|
||||
* */
|
||||
function registerPermit(
|
||||
string calldata ensName,
|
||||
uint256 stake,
|
||||
address[] calldata workersToRegister,
|
||||
address relayer,
|
||||
uint256 deadline,
|
||||
uint8 v,
|
||||
bytes32 r,
|
||||
bytes32 s
|
||||
) external {
|
||||
torn.permit(relayer, address(this), stake, deadline, v, r, s);
|
||||
_register(relayer, ensName, stake, workersToRegister);
|
||||
}
|
||||
|
||||
function _register(
|
||||
address relayer,
|
||||
string calldata ensName,
|
||||
uint256 stake,
|
||||
address[] calldata workersToRegister
|
||||
) internal {
|
||||
bytes32 ensHash = bytes(ensName).namehash();
|
||||
require(relayer == ens.owner(ensHash), "only ens owner");
|
||||
require(workers[relayer] == address(0), "cant register again");
|
||||
RelayerState storage metadata = relayers[relayer];
|
||||
|
||||
require(metadata.ensHash == bytes32(0), "registered already");
|
||||
require(stake >= minStakeAmount, "!min_stake");
|
||||
|
||||
torn.safeTransferFrom(relayer, address(staking), stake);
|
||||
emit StakeAddedToRelayer(relayer, stake);
|
||||
|
||||
metadata.balance = stake;
|
||||
metadata.ensHash = ensHash;
|
||||
workers[relayer] = relayer;
|
||||
|
||||
for (uint256 i = 0; i < workersToRegister.length; i++) {
|
||||
address worker = workersToRegister[i];
|
||||
_registerWorker(relayer, worker);
|
||||
modifier onlyGovernance() {
|
||||
require(msg.sender == governance, "only governance");
|
||||
_;
|
||||
}
|
||||
|
||||
emit RelayerRegistered(ensHash, ensName, relayer, stake);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This function should allow relayers to register more workeres
|
||||
* @param relayer Relayer which should send message from any worker which is already registered
|
||||
* @param worker Address to register
|
||||
* */
|
||||
function registerWorker(address relayer, address worker) external onlyRelayer(msg.sender, relayer) {
|
||||
_registerWorker(relayer, worker);
|
||||
}
|
||||
|
||||
function _registerWorker(address relayer, address worker) internal {
|
||||
require(workers[worker] == address(0), "can't steal an address");
|
||||
workers[worker] = relayer;
|
||||
emit WorkerRegistered(relayer, worker);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This function should allow anybody to unregister an address they own
|
||||
* @dev designed this way as to allow someone to unregister themselves in case a relayer misbehaves
|
||||
* - this should be followed by an action like burning relayer stake
|
||||
* - there was an option of allowing the sender to burn relayer stake in case of malicious behaviour, this feature was not included in the end
|
||||
* - reverts if trying to unregister master, otherwise contract would break. in general, there should be no reason to unregister master at all
|
||||
* */
|
||||
function unregisterWorker(address worker) external {
|
||||
if (worker != msg.sender) require(workers[worker] == msg.sender, "only owner of worker");
|
||||
require(workers[worker] != worker, "cant unregister master");
|
||||
emit WorkerUnregistered(workers[worker], worker);
|
||||
workers[worker] = address(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This function should allow anybody to stake to a relayer more TORN
|
||||
* @param relayer Relayer main address to stake to
|
||||
* @param stake Stake to be added to relayer
|
||||
* */
|
||||
function stakeToRelayer(address relayer, uint256 stake) external {
|
||||
_stakeToRelayer(msg.sender, relayer, stake);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev stakeToRelayer function equivalent with permit-approval instead of regular approve.
|
||||
* @param staker address from that stake is paid
|
||||
* */
|
||||
function stakeToRelayerPermit(
|
||||
address relayer,
|
||||
uint256 stake,
|
||||
address staker,
|
||||
uint256 deadline,
|
||||
uint8 v,
|
||||
bytes32 r,
|
||||
bytes32 s
|
||||
) external {
|
||||
torn.permit(staker, address(this), stake, deadline, v, r, s);
|
||||
_stakeToRelayer(staker, relayer, stake);
|
||||
}
|
||||
|
||||
function _stakeToRelayer(
|
||||
address staker,
|
||||
address relayer,
|
||||
uint256 stake
|
||||
) internal {
|
||||
require(workers[relayer] == relayer, "!registered");
|
||||
torn.safeTransferFrom(staker, address(staking), stake);
|
||||
relayers[relayer].balance = stake.add(relayers[relayer].balance);
|
||||
emit StakeAddedToRelayer(relayer, stake);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This function should burn some relayer stake on withdraw and notify staking of this
|
||||
* @dev IMPORTANT FUNCTION:
|
||||
* - This should be only called by the tornado proxy
|
||||
* - Should revert if relayer does not call proxy from valid worker
|
||||
* - Should not overflow
|
||||
* - Should underflow and revert (SafeMath) on not enough stake (balance)
|
||||
* @param sender worker to check sender == relayer
|
||||
* @param relayer address of relayer who's stake is being burned
|
||||
* @param pool instance to get fee for
|
||||
* */
|
||||
function burn(
|
||||
address sender,
|
||||
address relayer,
|
||||
ITornadoInstance pool
|
||||
) external onlyTornadoRouter {
|
||||
address masterAddress = workers[sender];
|
||||
if (masterAddress == address(0)) {
|
||||
require(workers[relayer] == address(0), "Only custom relayer");
|
||||
return;
|
||||
modifier onlyTornadoRouter() {
|
||||
require(msg.sender == tornadoRouter, "only proxy");
|
||||
_;
|
||||
}
|
||||
|
||||
require(masterAddress == relayer, "only relayer");
|
||||
uint256 toBurn = feeManager.instanceFeeWithUpdate(pool);
|
||||
relayers[relayer].balance = relayers[relayer].balance.sub(toBurn);
|
||||
staking.addBurnRewards(toBurn);
|
||||
emit StakeBurned(relayer, toBurn);
|
||||
}
|
||||
modifier onlyRelayer(address sender, address relayer) {
|
||||
require(workers[sender] == relayer, "only relayer");
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This function should allow governance to set the minimum stake amount
|
||||
* @param minAmount new minimum stake amount
|
||||
* */
|
||||
function setMinStakeAmount(uint256 minAmount) external onlyGovernance {
|
||||
minStakeAmount = minAmount;
|
||||
emit MinimumStakeAmount(minAmount);
|
||||
}
|
||||
constructor(address _torn, address _governance, address _ens, address _staking, address _feeManager) public {
|
||||
torn = TORN(_torn);
|
||||
governance = _governance;
|
||||
ens = IENS(_ens);
|
||||
staking = TornadoStakingRewards(_staking);
|
||||
feeManager = IFeeManager(_feeManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This function should allow governance to set a new tornado proxy address
|
||||
* @param tornadoRouterAddress address of the new proxy
|
||||
* */
|
||||
function setTornadoRouter(address tornadoRouterAddress) external onlyGovernance {
|
||||
tornadoRouter = tornadoRouterAddress;
|
||||
emit RouterRegistered(tornadoRouterAddress);
|
||||
}
|
||||
/**
|
||||
* @notice initialize function for upgradeability
|
||||
* @dev this contract will be deployed behind a proxy and should not assign values at logic address,
|
||||
* params left out because self explainable
|
||||
*
|
||||
*/
|
||||
function initialize(bytes32 _tornadoRouter) external initializer {
|
||||
tornadoRouter = resolve(_tornadoRouter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This function should allow governance to nullify a relayers balance
|
||||
* @dev IMPORTANT FUNCTION:
|
||||
* - Should nullify the balance
|
||||
* - Adding nullified balance as rewards was refactored to allow for the flexibility of these funds (for gov to operate with them)
|
||||
* @param relayer address of relayer who's balance is to nullify
|
||||
* */
|
||||
function nullifyBalance(address relayer) external onlyGovernance {
|
||||
address masterAddress = workers[relayer];
|
||||
require(relayer == masterAddress, "must be master");
|
||||
relayers[masterAddress].balance = 0;
|
||||
emit RelayerBalanceNullified(relayer);
|
||||
}
|
||||
/**
|
||||
* @notice This function should register a master address and optionally a set of workeres for a relayer + metadata
|
||||
* @dev Relayer can't steal other relayers workers since they are registered, and a wallet (msg.sender check) can always unregister itself
|
||||
* @param ensName ens name of the relayer
|
||||
* @param stake the initial amount of stake in TORN the relayer is depositing
|
||||
*
|
||||
*/
|
||||
function register(string calldata ensName, uint256 stake, address[] calldata workersToRegister) external {
|
||||
_register(msg.sender, ensName, stake, workersToRegister);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This function should check if a worker is associated with a relayer
|
||||
* @param toResolve address to check
|
||||
* @return true if is associated
|
||||
* */
|
||||
function isRelayer(address toResolve) external view returns (bool) {
|
||||
return workers[toResolve] != address(0);
|
||||
}
|
||||
/**
|
||||
* @dev Register function equivalent with permit-approval instead of regular approve.
|
||||
*
|
||||
*/
|
||||
function registerPermit(
|
||||
string calldata ensName,
|
||||
uint256 stake,
|
||||
address[] calldata workersToRegister,
|
||||
address relayer,
|
||||
uint256 deadline,
|
||||
uint8 v,
|
||||
bytes32 r,
|
||||
bytes32 s
|
||||
) external {
|
||||
torn.permit(relayer, address(this), stake, deadline, v, r, s);
|
||||
_register(relayer, ensName, stake, workersToRegister);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This function should check if a worker is registered to the relayer stated
|
||||
* @param relayer relayer to check
|
||||
* @param toResolve address to check
|
||||
* @return true if registered
|
||||
* */
|
||||
function isRelayerRegistered(address relayer, address toResolve) external view returns (bool) {
|
||||
return workers[toResolve] == relayer;
|
||||
}
|
||||
function _register(address relayer, string calldata ensName, uint256 stake, address[] calldata workersToRegister) internal {
|
||||
bytes32 ensHash = bytes(ensName).namehash();
|
||||
require(relayer == ens.owner(ensHash), "only ens owner");
|
||||
require(workers[relayer] == address(0), "cant register again");
|
||||
RelayerState storage metadata = relayers[relayer];
|
||||
|
||||
/**
|
||||
* @notice This function should get a relayers ensHash
|
||||
* @param relayer address to fetch for
|
||||
* @return relayer's ensHash
|
||||
* */
|
||||
function getRelayerEnsHash(address relayer) external view returns (bytes32) {
|
||||
return relayers[workers[relayer]].ensHash;
|
||||
}
|
||||
require(metadata.ensHash == bytes32(0), "registered already");
|
||||
require(stake >= minStakeAmount, "!min_stake");
|
||||
|
||||
/**
|
||||
* @notice This function should get a relayers balance
|
||||
* @param relayer relayer who's balance is to fetch
|
||||
* @return relayer's balance
|
||||
* */
|
||||
function getRelayerBalance(address relayer) external view returns (uint256) {
|
||||
return relayers[workers[relayer]].balance;
|
||||
}
|
||||
torn.safeTransferFrom(relayer, address(staking), stake);
|
||||
emit StakeAddedToRelayer(relayer, stake);
|
||||
|
||||
metadata.balance = stake;
|
||||
metadata.ensHash = ensHash;
|
||||
workers[relayer] = relayer;
|
||||
|
||||
for (uint256 i = 0; i < workersToRegister.length; i++) {
|
||||
address worker = workersToRegister[i];
|
||||
_registerWorker(relayer, worker);
|
||||
}
|
||||
|
||||
emit RelayerRegistered(ensHash, ensName, relayer, stake);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This function should allow relayers to register more workeres
|
||||
* @param relayer Relayer which should send message from any worker which is already registered
|
||||
* @param worker Address to register
|
||||
*
|
||||
*/
|
||||
function registerWorker(address relayer, address worker) external onlyRelayer(msg.sender, relayer) {
|
||||
_registerWorker(relayer, worker);
|
||||
}
|
||||
|
||||
function _registerWorker(address relayer, address worker) internal {
|
||||
require(workers[worker] == address(0), "can't steal an address");
|
||||
workers[worker] = relayer;
|
||||
emit WorkerRegistered(relayer, worker);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This function should allow anybody to unregister an address they own
|
||||
* @dev designed this way as to allow someone to unregister themselves in case a relayer misbehaves
|
||||
* - this should be followed by an action like burning relayer stake
|
||||
* - there was an option of allowing the sender to burn relayer stake in case of malicious behaviour, this feature was not included in the end
|
||||
* - reverts if trying to unregister master, otherwise contract would break. in general, there should be no reason to unregister master at all
|
||||
*
|
||||
*/
|
||||
function unregisterWorker(address worker) external {
|
||||
if (worker != msg.sender) require(workers[worker] == msg.sender, "only owner of worker");
|
||||
require(workers[worker] != worker, "cant unregister master");
|
||||
emit WorkerUnregistered(workers[worker], worker);
|
||||
workers[worker] = address(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This function should allow anybody to stake to a relayer more TORN
|
||||
* @param relayer Relayer main address to stake to
|
||||
* @param stake Stake to be added to relayer
|
||||
*
|
||||
*/
|
||||
function stakeToRelayer(address relayer, uint256 stake) external {
|
||||
_stakeToRelayer(msg.sender, relayer, stake);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev stakeToRelayer function equivalent with permit-approval instead of regular approve.
|
||||
* @param staker address from that stake is paid
|
||||
*
|
||||
*/
|
||||
function stakeToRelayerPermit(address relayer, uint256 stake, address staker, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
|
||||
external
|
||||
{
|
||||
torn.permit(staker, address(this), stake, deadline, v, r, s);
|
||||
_stakeToRelayer(staker, relayer, stake);
|
||||
}
|
||||
|
||||
function _stakeToRelayer(address staker, address relayer, uint256 stake) internal {
|
||||
require(workers[relayer] == relayer, "!registered");
|
||||
torn.safeTransferFrom(staker, address(staking), stake);
|
||||
relayers[relayer].balance = stake.add(relayers[relayer].balance);
|
||||
emit StakeAddedToRelayer(relayer, stake);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This function should burn some relayer stake on withdraw and notify staking of this
|
||||
* @dev IMPORTANT FUNCTION:
|
||||
* - This should be only called by the tornado proxy
|
||||
* - Should revert if relayer does not call proxy from valid worker
|
||||
* - Should not overflow
|
||||
* - Should underflow and revert (SafeMath) on not enough stake (balance)
|
||||
* @param sender worker to check sender == relayer
|
||||
* @param relayer address of relayer who's stake is being burned
|
||||
* @param pool instance to get fee for
|
||||
*
|
||||
*/
|
||||
function burn(address sender, address relayer, ITornadoInstance pool) external onlyTornadoRouter {
|
||||
address masterAddress = workers[sender];
|
||||
if (masterAddress == address(0)) {
|
||||
require(workers[relayer] == address(0), "Only custom relayer");
|
||||
return;
|
||||
}
|
||||
|
||||
require(masterAddress == relayer, "only relayer");
|
||||
uint256 toBurn = feeManager.instanceFeeWithUpdate(pool);
|
||||
relayers[relayer].balance = relayers[relayer].balance.sub(toBurn);
|
||||
staking.addBurnRewards(toBurn);
|
||||
emit StakeBurned(relayer, toBurn);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This function should allow governance to set the minimum stake amount
|
||||
* @param minAmount new minimum stake amount
|
||||
*
|
||||
*/
|
||||
function setMinStakeAmount(uint256 minAmount) external onlyGovernance {
|
||||
minStakeAmount = minAmount;
|
||||
emit MinimumStakeAmount(minAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This function should allow governance to set a new tornado proxy address
|
||||
* @param tornadoRouterAddress address of the new proxy
|
||||
*
|
||||
*/
|
||||
function setTornadoRouter(address tornadoRouterAddress) external onlyGovernance {
|
||||
tornadoRouter = tornadoRouterAddress;
|
||||
emit RouterRegistered(tornadoRouterAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This function should allow governance to nullify a relayers balance
|
||||
* @dev IMPORTANT FUNCTION:
|
||||
* - Should nullify the balance
|
||||
* - Adding nullified balance as rewards was refactored to allow for the flexibility of these funds (for gov to operate with them)
|
||||
* @param relayer address of relayer who's balance is to nullify
|
||||
*
|
||||
*/
|
||||
function nullifyBalance(address relayer) external onlyGovernance {
|
||||
address masterAddress = workers[relayer];
|
||||
require(relayer == masterAddress, "must be master");
|
||||
relayers[masterAddress].balance = 0;
|
||||
emit RelayerBalanceNullified(relayer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This function should check if a worker is associated with a relayer
|
||||
* @param toResolve address to check
|
||||
* @return true if is associated
|
||||
*
|
||||
*/
|
||||
function isRelayer(address toResolve) external view returns (bool) {
|
||||
return workers[toResolve] != address(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This function should check if a worker is registered to the relayer stated
|
||||
* @param relayer relayer to check
|
||||
* @param toResolve address to check
|
||||
* @return true if registered
|
||||
*
|
||||
*/
|
||||
function isRelayerRegistered(address relayer, address toResolve) external view returns (bool) {
|
||||
return workers[toResolve] == relayer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This function should get a relayers ensHash
|
||||
* @param relayer address to fetch for
|
||||
* @return relayer's ensHash
|
||||
*
|
||||
*/
|
||||
function getRelayerEnsHash(address relayer) external view returns (bytes32) {
|
||||
return relayers[workers[relayer]].ensHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This function should get a relayers balance
|
||||
* @param relayer relayer who's balance is to fetch
|
||||
* @return relayer's balance
|
||||
*
|
||||
*/
|
||||
function getRelayerBalance(address relayer) external view returns (uint256) {
|
||||
return relayers[workers[relayer]].balance;
|
||||
}
|
||||
}
|
||||
|
@ -10,13 +10,13 @@ import { Initializable } from "@openzeppelin/contracts/proxy/Initializable.sol";
|
||||
import { EnsResolve } from "torn-token/contracts/ENS.sol";
|
||||
|
||||
interface ITornadoVault {
|
||||
function withdrawTorn(address recipient, uint256 amount) external;
|
||||
function withdrawTorn(address recipient, uint256 amount) external;
|
||||
}
|
||||
|
||||
interface ITornadoGovernance {
|
||||
function lockedBalance(address account) external view returns (uint256);
|
||||
function lockedBalance(address account) external view returns (uint256);
|
||||
|
||||
function userVault() external view returns (ITornadoVault);
|
||||
function userVault() external view returns (ITornadoVault);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -25,119 +25,118 @@ interface ITornadoGovernance {
|
||||
* and properly attribute rewards to addresses without security issues.
|
||||
* @dev CONTRACT RISKS:
|
||||
* - Relayer staked TORN at risk if contract is compromised.
|
||||
* */
|
||||
*
|
||||
*/
|
||||
contract TornadoStakingRewards is Initializable, EnsResolve {
|
||||
using SafeMath for uint256;
|
||||
using SafeERC20 for IERC20;
|
||||
using SafeMath for uint256;
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
/// @notice 1e25
|
||||
uint256 public immutable ratioConstant;
|
||||
ITornadoGovernance public immutable Governance;
|
||||
IERC20 public immutable torn;
|
||||
address public immutable relayerRegistry;
|
||||
/// @notice 1e25
|
||||
uint256 public immutable ratioConstant;
|
||||
ITornadoGovernance public immutable Governance;
|
||||
IERC20 public immutable torn;
|
||||
address public immutable relayerRegistry;
|
||||
|
||||
/// @notice the sum torn_burned_i/locked_amount_i*coefficient where i is incremented at each burn
|
||||
uint256 public accumulatedRewardPerTorn;
|
||||
/// @notice notes down accumulatedRewardPerTorn for an address on a lock/unlock/claim
|
||||
mapping(address => uint256) public accumulatedRewardRateOnLastUpdate;
|
||||
/// @notice notes down how much an account may claim
|
||||
mapping(address => uint256) public accumulatedRewards;
|
||||
/// @notice the sum torn_burned_i/locked_amount_i*coefficient where i is incremented at each burn
|
||||
uint256 public accumulatedRewardPerTorn;
|
||||
/// @notice notes down accumulatedRewardPerTorn for an address on a lock/unlock/claim
|
||||
mapping(address => uint256) public accumulatedRewardRateOnLastUpdate;
|
||||
/// @notice notes down how much an account may claim
|
||||
mapping(address => uint256) public accumulatedRewards;
|
||||
|
||||
event RewardsUpdated(address indexed account, uint256 rewards);
|
||||
event RewardsClaimed(address indexed account, uint256 rewardsClaimed);
|
||||
event RewardsUpdated(address indexed account, uint256 rewards);
|
||||
event RewardsClaimed(address indexed account, uint256 rewardsClaimed);
|
||||
|
||||
modifier onlyGovernance() {
|
||||
require(msg.sender == address(Governance), "only governance");
|
||||
_;
|
||||
}
|
||||
modifier onlyGovernance() {
|
||||
require(msg.sender == address(Governance), "only governance");
|
||||
_;
|
||||
}
|
||||
|
||||
// Minor code change here we won't resolve the registry by ENS
|
||||
constructor(
|
||||
address governanceAddress,
|
||||
address tornAddress,
|
||||
address _relayerRegistry
|
||||
) public {
|
||||
Governance = ITornadoGovernance(governanceAddress);
|
||||
torn = IERC20(tornAddress);
|
||||
relayerRegistry = _relayerRegistry;
|
||||
ratioConstant = IERC20(tornAddress).totalSupply();
|
||||
}
|
||||
// Minor code change here we won't resolve the registry by ENS
|
||||
constructor(address governanceAddress, address tornAddress, address _relayerRegistry) public {
|
||||
Governance = ITornadoGovernance(governanceAddress);
|
||||
torn = IERC20(tornAddress);
|
||||
relayerRegistry = _relayerRegistry;
|
||||
ratioConstant = IERC20(tornAddress).totalSupply();
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This function should safely send a user his rewards.
|
||||
* @dev IMPORTANT FUNCTION:
|
||||
* We know that rewards are going to be updated every time someone locks or unlocks
|
||||
* so we know that this function can't be used to falsely increase the amount of
|
||||
* lockedTorn by locking in governance and subsequently calling it.
|
||||
* - set rewards to 0 greedily
|
||||
*/
|
||||
function getReward() external {
|
||||
uint256 rewards = _updateReward(msg.sender, Governance.lockedBalance(msg.sender));
|
||||
rewards = rewards.add(accumulatedRewards[msg.sender]);
|
||||
accumulatedRewards[msg.sender] = 0;
|
||||
torn.safeTransfer(msg.sender, rewards);
|
||||
emit RewardsClaimed(msg.sender, rewards);
|
||||
}
|
||||
/**
|
||||
* @notice This function should safely send a user his rewards.
|
||||
* @dev IMPORTANT FUNCTION:
|
||||
* We know that rewards are going to be updated every time someone locks or unlocks
|
||||
* so we know that this function can't be used to falsely increase the amount of
|
||||
* lockedTorn by locking in governance and subsequently calling it.
|
||||
* - set rewards to 0 greedily
|
||||
*/
|
||||
function getReward() external {
|
||||
uint256 rewards = _updateReward(msg.sender, Governance.lockedBalance(msg.sender));
|
||||
rewards = rewards.add(accumulatedRewards[msg.sender]);
|
||||
accumulatedRewards[msg.sender] = 0;
|
||||
torn.safeTransfer(msg.sender, rewards);
|
||||
emit RewardsClaimed(msg.sender, rewards);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This function should increment the proper amount of rewards per torn for the contract
|
||||
* @dev IMPORTANT FUNCTION:
|
||||
* - calculation must not overflow with extreme values
|
||||
* (amount <= 1e25) * 1e25 / (balance of vault <= 1e25) -> (extreme values)
|
||||
* @param amount amount to add to the rewards
|
||||
*/
|
||||
function addBurnRewards(uint256 amount) external {
|
||||
require(msg.sender == address(Governance) || msg.sender == relayerRegistry, "unauthorized");
|
||||
accumulatedRewardPerTorn = accumulatedRewardPerTorn.add(
|
||||
amount.mul(ratioConstant).div(torn.balanceOf(address(Governance.userVault())))
|
||||
);
|
||||
}
|
||||
/**
|
||||
* @notice This function should increment the proper amount of rewards per torn for the contract
|
||||
* @dev IMPORTANT FUNCTION:
|
||||
* - calculation must not overflow with extreme values
|
||||
* (amount <= 1e25) * 1e25 / (balance of vault <= 1e25) -> (extreme values)
|
||||
* @param amount amount to add to the rewards
|
||||
*/
|
||||
function addBurnRewards(uint256 amount) external {
|
||||
require(msg.sender == address(Governance) || msg.sender == relayerRegistry, "unauthorized");
|
||||
accumulatedRewardPerTorn =
|
||||
accumulatedRewardPerTorn.add(amount.mul(ratioConstant).div(torn.balanceOf(address(Governance.userVault()))));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This function should allow governance to properly update the accumulated rewards rate for an account
|
||||
* @param account address of account to update data for
|
||||
* @param amountLockedBeforehand the balance locked beforehand in the governance contract
|
||||
* */
|
||||
function updateRewardsOnLockedBalanceChange(address account, uint256 amountLockedBeforehand) external onlyGovernance {
|
||||
uint256 claimed = _updateReward(account, amountLockedBeforehand);
|
||||
accumulatedRewards[account] = accumulatedRewards[account].add(claimed);
|
||||
}
|
||||
/**
|
||||
* @notice This function should allow governance to properly update the accumulated rewards rate for an account
|
||||
* @param account address of account to update data for
|
||||
* @param amountLockedBeforehand the balance locked beforehand in the governance contract
|
||||
*
|
||||
*/
|
||||
function updateRewardsOnLockedBalanceChange(address account, uint256 amountLockedBeforehand) external onlyGovernance {
|
||||
uint256 claimed = _updateReward(account, amountLockedBeforehand);
|
||||
accumulatedRewards[account] = accumulatedRewards[account].add(claimed);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This function should allow governance rescue tokens from the staking rewards contract
|
||||
* */
|
||||
function withdrawTorn(uint256 amount) external onlyGovernance {
|
||||
if (amount == type(uint256).max) amount = torn.balanceOf(address(this));
|
||||
torn.safeTransfer(address(Governance), amount);
|
||||
}
|
||||
/**
|
||||
* @notice This function should allow governance rescue tokens from the staking rewards contract
|
||||
*
|
||||
*/
|
||||
function withdrawTorn(uint256 amount) external onlyGovernance {
|
||||
if (amount == type(uint256).max) amount = torn.balanceOf(address(this));
|
||||
torn.safeTransfer(address(Governance), amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This function should calculated the proper amount of rewards attributed to user since the last update
|
||||
* @dev IMPORTANT FUNCTION:
|
||||
* - calculation must not overflow with extreme values
|
||||
* (accumulatedReward <= 1e25) * (lockedBeforehand <= 1e25) / 1e25
|
||||
* - result may go to 0, since this implies on 1 TORN locked => accumulatedReward <= 1e7, meaning a very small reward
|
||||
* @param account address of account to calculate rewards for
|
||||
* @param amountLockedBeforehand the balance locked beforehand in the governance contract
|
||||
* @return claimed the rewards attributed to user since the last update
|
||||
*/
|
||||
function _updateReward(address account, uint256 amountLockedBeforehand) private returns (uint256 claimed) {
|
||||
if (amountLockedBeforehand != 0)
|
||||
claimed = (accumulatedRewardPerTorn.sub(accumulatedRewardRateOnLastUpdate[account])).mul(amountLockedBeforehand).div(
|
||||
ratioConstant
|
||||
);
|
||||
accumulatedRewardRateOnLastUpdate[account] = accumulatedRewardPerTorn;
|
||||
emit RewardsUpdated(account, claimed);
|
||||
}
|
||||
/**
|
||||
* @notice This function should calculated the proper amount of rewards attributed to user since the last update
|
||||
* @dev IMPORTANT FUNCTION:
|
||||
* - calculation must not overflow with extreme values
|
||||
* (accumulatedReward <= 1e25) * (lockedBeforehand <= 1e25) / 1e25
|
||||
* - result may go to 0, since this implies on 1 TORN locked => accumulatedReward <= 1e7, meaning a very small reward
|
||||
* @param account address of account to calculate rewards for
|
||||
* @param amountLockedBeforehand the balance locked beforehand in the governance contract
|
||||
* @return claimed the rewards attributed to user since the last update
|
||||
*/
|
||||
function _updateReward(address account, uint256 amountLockedBeforehand) private returns (uint256 claimed) {
|
||||
if (amountLockedBeforehand != 0) {
|
||||
claimed =
|
||||
(accumulatedRewardPerTorn.sub(accumulatedRewardRateOnLastUpdate[account])).mul(amountLockedBeforehand).div(ratioConstant);
|
||||
}
|
||||
accumulatedRewardRateOnLastUpdate[account] = accumulatedRewardPerTorn;
|
||||
emit RewardsUpdated(account, claimed);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This function should show a user his rewards.
|
||||
* @param account address of account to calculate rewards for
|
||||
*/
|
||||
function checkReward(address account) external view returns (uint256 rewards) {
|
||||
uint256 amountLocked = Governance.lockedBalance(account);
|
||||
if (amountLocked != 0)
|
||||
rewards = (accumulatedRewardPerTorn.sub(accumulatedRewardRateOnLastUpdate[account])).mul(amountLocked).div(ratioConstant);
|
||||
rewards = rewards.add(accumulatedRewards[account]);
|
||||
}
|
||||
/**
|
||||
* @notice This function should show a user his rewards.
|
||||
* @param account address of account to calculate rewards for
|
||||
*/
|
||||
function checkReward(address account) external view returns (uint256 rewards) {
|
||||
uint256 amountLocked = Governance.lockedBalance(account);
|
||||
if (amountLocked != 0) {
|
||||
rewards = (accumulatedRewardPerTorn.sub(accumulatedRewardRateOnLastUpdate[account])).mul(amountLocked).div(ratioConstant);
|
||||
}
|
||||
rewards = rewards.add(accumulatedRewards[account]);
|
||||
}
|
||||
}
|
||||
|
@ -3,11 +3,11 @@
|
||||
pragma solidity 0.6.12;
|
||||
|
||||
interface IMetamorphicContractFactory {
|
||||
function findMetamorphicContractAddress(bytes32 salt) external view returns (address metamorphicContractAddress);
|
||||
function findMetamorphicContractAddress(bytes32 salt) external view returns (address metamorphicContractAddress);
|
||||
|
||||
function deployMetamorphicContractFromExistingImplementation(
|
||||
bytes32 salt,
|
||||
address implementationContract,
|
||||
bytes calldata metamorphicContractInitializationCalldata
|
||||
) external payable returns (address metamorphicContractAddress);
|
||||
}
|
||||
function deployMetamorphicContractFromExistingImplementation(
|
||||
bytes32 salt,
|
||||
address implementationContract,
|
||||
bytes calldata metamorphicContractInitializationCalldata
|
||||
) external payable returns (address metamorphicContractAddress);
|
||||
}
|
||||
|
@ -6,27 +6,27 @@ pragma experimental ABIEncoderV2;
|
||||
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
|
||||
contract InitialProposal {
|
||||
event MockExecuted(uint256 num);
|
||||
event MockExecuted(uint256 num);
|
||||
|
||||
function executeProposal() external virtual {
|
||||
emit MockExecuted(1);
|
||||
}
|
||||
function executeProposal() external virtual {
|
||||
emit MockExecuted(1);
|
||||
}
|
||||
|
||||
function emergencyStop() public {
|
||||
selfdestruct(payable(0));
|
||||
}
|
||||
function emergencyStop() public {
|
||||
selfdestruct(payable(0));
|
||||
}
|
||||
}
|
||||
|
||||
contract MaliciousProposal is InitialProposal {
|
||||
address public immutable deployer;
|
||||
address public immutable deployer;
|
||||
|
||||
constructor() public {
|
||||
deployer = msg.sender;
|
||||
}
|
||||
constructor() public {
|
||||
deployer = msg.sender;
|
||||
}
|
||||
|
||||
function executeProposal() external virtual override {
|
||||
IERC20 torn = IERC20(0x77777FeDdddFfC19Ff86DB637967013e6C6A116C);
|
||||
uint256 bal = torn.balanceOf(address(this));
|
||||
torn.transfer(deployer, bal);
|
||||
}
|
||||
function executeProposal() external virtual override {
|
||||
IERC20 torn = IERC20(0x77777FeDdddFfC19Ff86DB637967013e6C6A116C);
|
||||
uint256 bal = torn.balanceOf(address(this));
|
||||
torn.transfer(deployer, bal);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user