include tests with metamorphic factory
Signed-off-by: AlienTornadosaurusHex <>
This commit is contained in:
parent
14f9e379ec
commit
c8507739d2
@ -1,3 +1,12 @@
|
|||||||
|
# Governance upgrade to patch exploit vulnerability
|
||||||
|
|
||||||
|
The contracts can be currently found here:
|
||||||
|
|
||||||
|
* [GovernancePatchUpgrade](./contracts/v4-patch/GovernancePatchUpgrade.sol)
|
||||||
|
* [PatchProposal](./contracts/v4-patch/PatchProposal.sol)
|
||||||
|
|
||||||
|
Inlined version of the `RelayerRegistry` and `TornadoStakingRewards` are also used. Check the `diffs` folder to see how much they deviate from the `relayer-registry` repository reconstruction. VSC will possibly show red as "added".
|
||||||
|
|
||||||
# Tornado governance [![build status](https://github.com/tornadocash/tornado-governance/actions/workflows/build.yml/badge.svg)](https://github.com/tornadocash/tornado-governance/actions/workflows/build.yml) [![Coverage Status](https://coveralls.io/repos/github/tornadocash/tornado-governance/badge.svg?branch=master)](https://coveralls.io/github/tornadocash/tornado-governance?branch=master)
|
# Tornado governance [![build status](https://github.com/tornadocash/tornado-governance/actions/workflows/build.yml/badge.svg)](https://github.com/tornadocash/tornado-governance/actions/workflows/build.yml) [![Coverage Status](https://coveralls.io/repos/github/tornadocash/tornado-governance/badge.svg?branch=master)](https://coveralls.io/github/tornadocash/tornado-governance?branch=master)
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
@ -35,10 +35,13 @@ contract GovernancePatchUpgrade is GovernanceStakingUpgrade {
|
|||||||
if (proposalCodehash == proposalCodehashes[proposalId]) {
|
if (proposalCodehash == proposalCodehashes[proposalId]) {
|
||||||
super.execute(proposalId);
|
super.execute(proposalId);
|
||||||
} else {
|
} else {
|
||||||
// Note that this is the easiest way to block further execution
|
// So this should guarantee that the proposal should not be able to be executed in future
|
||||||
|
// But also, the proposal will be skipped each time. So the question is whether this, a silent
|
||||||
|
// skip, or a require can be used to stay "concise", and abandon the safety of having also this assign
|
||||||
|
// Outside dependencies, which don't exist as of yet, might believe that the proposal code was executed
|
||||||
proposal.executed = true;
|
proposal.executed = true;
|
||||||
|
|
||||||
// Let the event signify it was broken
|
// Let the event signify it was metamorphic
|
||||||
emit CodehashDifferent(target, proposalCodehashes[proposalId], proposalCodehash);
|
emit CodehashDifferent(target, proposalCodehashes[proposalId], proposalCodehash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ interface Proxy {
|
|||||||
|
|
||||||
// We will have to do this because of the contract size limit
|
// We will have to do this because of the contract size limit
|
||||||
|
|
||||||
contract ProposalContractFactory {
|
contract ProposalContractsFactory {
|
||||||
function createStakingRewards(
|
function createStakingRewards(
|
||||||
address governance,
|
address governance,
|
||||||
address torn,
|
address torn,
|
||||||
@ -48,10 +48,10 @@ contract PatchProposal {
|
|||||||
|
|
||||||
IERC20 public constant TORN = IERC20(0x77777FeDdddFfC19Ff86DB637967013e6C6A116C);
|
IERC20 public constant TORN = IERC20(0x77777FeDdddFfC19Ff86DB637967013e6C6A116C);
|
||||||
|
|
||||||
ProposalContractFactory public immutable proposalContractFactory;
|
ProposalContractsFactory public immutable proposalContractFactory;
|
||||||
|
|
||||||
constructor(address _proposalContractFactory) public {
|
constructor(address _proposalContractFactory) public {
|
||||||
proposalContractFactory = ProposalContractFactory(_proposalContractFactory);
|
proposalContractFactory = ProposalContractsFactory(_proposalContractFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aight lets do this sirs
|
// Aight lets do this sirs
|
||||||
|
566
contracts/v4-patch/metamorphic/MetamorphicContractFactory.sol
Normal file
566
contracts/v4-patch/metamorphic/MetamorphicContractFactory.sol
Normal file
@ -0,0 +1,566 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pragma solidity 0.5.6;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title Metamorphic Contract Factory
|
||||||
|
* @author 0age
|
||||||
|
* @notice This contract creates metamorphic contracts, or contracts that can be
|
||||||
|
* redeployed with new code to the same address. It does so by deploying a
|
||||||
|
* contract with fixed, non-deterministic initialization code via the CREATE2
|
||||||
|
* opcode. This contract clones the implementation contract in its constructor.
|
||||||
|
* Once a contract undergoes metamorphosis, all existing storage will be deleted
|
||||||
|
* and any existing contract code will be replaced with the deployed contract
|
||||||
|
* code of the new implementation contract.
|
||||||
|
* @dev CREATE2 will not be available on mainnet until (at least) block
|
||||||
|
* 7,280,000. This contract has not yet been fully tested or audited - proceed
|
||||||
|
* with caution and please share any exploits or optimizations you discover.
|
||||||
|
*/
|
||||||
|
contract MetamorphicContractFactory {
|
||||||
|
// fires when a metamorphic contract is deployed by cloning another contract.
|
||||||
|
event Metamorphosed(address metamorphicContract, address newImplementation);
|
||||||
|
|
||||||
|
// fires when a metamorphic contract is deployed through a transient contract.
|
||||||
|
event MetamorphosedWithConstructor(address metamorphicContract, address transientContract);
|
||||||
|
|
||||||
|
// store the initialization code for metamorphic contracts.
|
||||||
|
bytes private _metamorphicContractInitializationCode;
|
||||||
|
|
||||||
|
// store hash of the initialization code for metamorphic contracts as well.
|
||||||
|
bytes32 private _metamorphicContractInitializationCodeHash;
|
||||||
|
|
||||||
|
// store init code for transient contracts that deploy metamorphic contracts.
|
||||||
|
bytes private _transientContractInitializationCode;
|
||||||
|
|
||||||
|
// store the hash of the initialization code for transient contracts as well.
|
||||||
|
bytes32 private _transientContractInitializationCodeHash;
|
||||||
|
|
||||||
|
// maintain a mapping of metamorphic contracts to metamorphic implementations.
|
||||||
|
mapping(address => address) private _implementations;
|
||||||
|
|
||||||
|
// maintain a mapping of transient contracts to metamorphic init codes.
|
||||||
|
mapping(address => bytes) private _initCodes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev In the constructor, set up the initialization code for metamorphic
|
||||||
|
* contracts as well as the keccak256 hash of the given initialization code.
|
||||||
|
* @param transientContractInitializationCode bytes The initialization code
|
||||||
|
* that will be used to deploy any transient contracts, which will deploy any
|
||||||
|
* metamorphic contracts that require the use of a constructor.
|
||||||
|
*
|
||||||
|
* Metamorphic contract initialization code (29 bytes):
|
||||||
|
*
|
||||||
|
* 0x5860208158601c335a63aaf10f428752fa158151803b80938091923cf3
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
*
|
||||||
|
* pc|op|name | [stack] | <memory>
|
||||||
|
*
|
||||||
|
* ** set the first stack item to zero - used later **
|
||||||
|
* 00 58 getpc [0] <>
|
||||||
|
*
|
||||||
|
* ** set second stack item to 32, length of word returned from staticcall **
|
||||||
|
* 01 60 push1
|
||||||
|
* 02 20 outsize [0, 32] <>
|
||||||
|
*
|
||||||
|
* ** set third stack item to 0, position of word returned from staticcall **
|
||||||
|
* 03 81 dup2 [0, 32, 0] <>
|
||||||
|
*
|
||||||
|
* ** set fourth stack item to 4, length of selector given to staticcall **
|
||||||
|
* 04 58 getpc [0, 32, 0, 4] <>
|
||||||
|
*
|
||||||
|
* ** set fifth stack item to 28, position of selector given to staticcall **
|
||||||
|
* 05 60 push1
|
||||||
|
* 06 1c inpos [0, 32, 0, 4, 28] <>
|
||||||
|
*
|
||||||
|
* ** set the sixth stack item to msg.sender, target address for staticcall **
|
||||||
|
* 07 33 caller [0, 32, 0, 4, 28, caller] <>
|
||||||
|
*
|
||||||
|
* ** set the seventh stack item to msg.gas, gas to forward for staticcall **
|
||||||
|
* 08 5a gas [0, 32, 0, 4, 28, caller, gas] <>
|
||||||
|
*
|
||||||
|
* ** set the eighth stack item to selector, "what" to store via mstore **
|
||||||
|
* 09 63 push4
|
||||||
|
* 10 aaf10f42 selector [0, 32, 0, 4, 28, caller, gas, 0xaaf10f42] <>
|
||||||
|
*
|
||||||
|
* ** set the ninth stack item to 0, "where" to store via mstore ***
|
||||||
|
* 11 87 dup8 [0, 32, 0, 4, 28, caller, gas, 0xaaf10f42, 0] <>
|
||||||
|
*
|
||||||
|
* ** call mstore, consume 8 and 9 from the stack, place selector in memory **
|
||||||
|
* 12 52 mstore [0, 32, 0, 4, 0, caller, gas] <0xaaf10f42>
|
||||||
|
*
|
||||||
|
* ** call staticcall, consume items 2 through 7, place address in memory **
|
||||||
|
* 13 fa staticcall [0, 1 (if successful)] <address>
|
||||||
|
*
|
||||||
|
* ** flip success bit in second stack item to set to 0 **
|
||||||
|
* 14 15 iszero [0, 0] <address>
|
||||||
|
*
|
||||||
|
* ** push a third 0 to the stack, position of address in memory **
|
||||||
|
* 15 81 dup2 [0, 0, 0] <address>
|
||||||
|
*
|
||||||
|
* ** place address from position in memory onto third stack item **
|
||||||
|
* 16 51 mload [0, 0, address] <>
|
||||||
|
*
|
||||||
|
* ** place address to fourth stack item for extcodesize to consume **
|
||||||
|
* 17 80 dup1 [0, 0, address, address] <>
|
||||||
|
*
|
||||||
|
* ** get extcodesize on fourth stack item for extcodecopy **
|
||||||
|
* 18 3b extcodesize [0, 0, address, size] <>
|
||||||
|
*
|
||||||
|
* ** dup and swap size for use by return at end of init code **
|
||||||
|
* 19 80 dup1 [0, 0, address, size, size] <>
|
||||||
|
* 20 93 swap4 [size, 0, address, size, 0] <>
|
||||||
|
*
|
||||||
|
* ** push code position 0 to stack and reorder stack items for extcodecopy **
|
||||||
|
* 21 80 dup1 [size, 0, address, size, 0, 0] <>
|
||||||
|
* 22 91 swap2 [size, 0, address, 0, 0, size] <>
|
||||||
|
* 23 92 swap3 [size, 0, size, 0, 0, address] <>
|
||||||
|
*
|
||||||
|
* ** call extcodecopy, consume four items, clone runtime code to memory **
|
||||||
|
* 24 3c extcodecopy [size, 0] <code>
|
||||||
|
*
|
||||||
|
* ** return to deploy final code in memory **
|
||||||
|
* 25 f3 return [] *deployed!*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Transient contract initialization code derived from TransientContract.sol.
|
||||||
|
*/
|
||||||
|
constructor(bytes memory transientContractInitializationCode) public {
|
||||||
|
// assign the initialization code for the metamorphic contract.
|
||||||
|
_metamorphicContractInitializationCode = (hex"5860208158601c335a63aaf10f428752fa158151803b80938091923cf3");
|
||||||
|
|
||||||
|
// calculate and assign keccak256 hash of metamorphic initialization code.
|
||||||
|
_metamorphicContractInitializationCodeHash = keccak256(abi.encodePacked(_metamorphicContractInitializationCode));
|
||||||
|
|
||||||
|
// store the initialization code for the transient contract.
|
||||||
|
_transientContractInitializationCode = transientContractInitializationCode;
|
||||||
|
|
||||||
|
// calculate and assign keccak256 hash of transient initialization code.
|
||||||
|
_transientContractInitializationCodeHash = keccak256(abi.encodePacked(_transientContractInitializationCode));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* solhint-disable function-max-lines */
|
||||||
|
/**
|
||||||
|
* @dev Deploy a metamorphic contract by submitting a given salt or nonce
|
||||||
|
* along with the initialization code for the metamorphic contract, and
|
||||||
|
* optionally provide calldata for initializing the new metamorphic contract.
|
||||||
|
* To replace the contract, first selfdestruct the current contract, then call
|
||||||
|
* with the same salt value and new initialization code (be aware that all
|
||||||
|
* existing state will be wiped from the existing contract). Also note that
|
||||||
|
* the first 20 bytes of the salt must match the calling address, which
|
||||||
|
* prevents contracts from being created by unintended parties.
|
||||||
|
* @param salt bytes32 The nonce that will be passed into the CREATE2 call and
|
||||||
|
* thus will determine the resulant address of the metamorphic contract.
|
||||||
|
* @param implementationContractInitializationCode bytes The initialization
|
||||||
|
* code for the implementation contract for the metamorphic contract. It will
|
||||||
|
* be used to deploy a new contract that the metamorphic contract will then
|
||||||
|
* clone in its constructor.
|
||||||
|
* @param metamorphicContractInitializationCalldata bytes An optional data
|
||||||
|
* parameter that can be used to atomically initialize the metamorphic
|
||||||
|
* contract.
|
||||||
|
* @return Address of the metamorphic contract that will be created.
|
||||||
|
*/
|
||||||
|
function deployMetamorphicContract(
|
||||||
|
bytes32 salt,
|
||||||
|
bytes calldata implementationContractInitializationCode,
|
||||||
|
bytes calldata metamorphicContractInitializationCalldata
|
||||||
|
) external payable containsCaller(salt) returns (address metamorphicContractAddress) {
|
||||||
|
// move implementation init code and initialization calldata to memory.
|
||||||
|
bytes memory implInitCode = implementationContractInitializationCode;
|
||||||
|
bytes memory data = metamorphicContractInitializationCalldata;
|
||||||
|
|
||||||
|
// move the initialization code from storage to memory.
|
||||||
|
bytes memory initCode = _metamorphicContractInitializationCode;
|
||||||
|
|
||||||
|
// declare variable to verify successful metamorphic contract deployment.
|
||||||
|
address deployedMetamorphicContract;
|
||||||
|
|
||||||
|
// determine the address of the metamorphic contract.
|
||||||
|
metamorphicContractAddress = _getMetamorphicContractAddress(salt);
|
||||||
|
|
||||||
|
// declare a variable for the address of the implementation contract.
|
||||||
|
address implementationContract;
|
||||||
|
|
||||||
|
// load implementation init code and length, then deploy via CREATE.
|
||||||
|
/* solhint-disable no-inline-assembly */
|
||||||
|
assembly {
|
||||||
|
let encoded_data := add(0x20, implInitCode) // load initialization code.
|
||||||
|
let encoded_size := mload(implInitCode) // load init code's length.
|
||||||
|
implementationContract := create(
|
||||||
|
// call CREATE with 3 arguments.
|
||||||
|
0, // do not forward any endowment.
|
||||||
|
encoded_data, // pass in initialization code.
|
||||||
|
encoded_size // pass in init code's length.
|
||||||
|
)
|
||||||
|
} /* solhint-enable no-inline-assembly */
|
||||||
|
|
||||||
|
require(implementationContract != address(0), "Could not deploy implementation.");
|
||||||
|
|
||||||
|
// store the implementation to be retrieved by the metamorphic contract.
|
||||||
|
_implementations[metamorphicContractAddress] = implementationContract;
|
||||||
|
|
||||||
|
// load metamorphic contract data and length of data and deploy via CREATE2.
|
||||||
|
/* solhint-disable no-inline-assembly */
|
||||||
|
assembly {
|
||||||
|
let encoded_data := add(0x20, initCode) // load initialization code.
|
||||||
|
let encoded_size := mload(initCode) // load the init code's length.
|
||||||
|
deployedMetamorphicContract := create2(
|
||||||
|
// call CREATE2 with 4 arguments.
|
||||||
|
0, // do not forward any endowment.
|
||||||
|
encoded_data, // pass in initialization code.
|
||||||
|
encoded_size, // pass in init code's length.
|
||||||
|
salt // pass in the salt value.
|
||||||
|
)
|
||||||
|
} /* solhint-enable no-inline-assembly */
|
||||||
|
|
||||||
|
// ensure that the contracts were successfully deployed.
|
||||||
|
require(deployedMetamorphicContract == metamorphicContractAddress, "Failed to deploy the new metamorphic contract.");
|
||||||
|
|
||||||
|
// initialize the new metamorphic contract if any data or value is provided.
|
||||||
|
if (data.length > 0 || msg.value > 0) {
|
||||||
|
/* solhint-disable avoid-call-value */
|
||||||
|
(bool success, ) = deployedMetamorphicContract.call.value(msg.value)(data);
|
||||||
|
/* solhint-enable avoid-call-value */
|
||||||
|
|
||||||
|
require(success, "Failed to initialize the new metamorphic contract.");
|
||||||
|
}
|
||||||
|
|
||||||
|
emit Metamorphosed(deployedMetamorphicContract, implementationContract);
|
||||||
|
} /* solhint-enable function-max-lines */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Deploy a metamorphic contract by submitting a given salt or nonce
|
||||||
|
* along with the address of an existing implementation contract to clone, and
|
||||||
|
* optionally provide calldata for initializing the new metamorphic contract.
|
||||||
|
* To replace the contract, first selfdestruct the current contract, then call
|
||||||
|
* with the same salt value and a new implementation address (be aware that
|
||||||
|
* all existing state will be wiped from the existing contract). Also note
|
||||||
|
* that the first 20 bytes of the salt must match the calling address, which
|
||||||
|
* prevents contracts from being created by unintended parties.
|
||||||
|
* @param salt bytes32 The nonce that will be passed into the CREATE2 call and
|
||||||
|
* thus will determine the resulant address of the metamorphic contract.
|
||||||
|
* @param implementationContract address The address of the existing
|
||||||
|
* implementation contract to clone.
|
||||||
|
* @param metamorphicContractInitializationCalldata bytes An optional data
|
||||||
|
* parameter that can be used to atomically initialize the metamorphic
|
||||||
|
* contract.
|
||||||
|
* @return Address of the metamorphic contract that will be created.
|
||||||
|
*/
|
||||||
|
function deployMetamorphicContractFromExistingImplementation(
|
||||||
|
bytes32 salt,
|
||||||
|
address implementationContract,
|
||||||
|
bytes calldata metamorphicContractInitializationCalldata
|
||||||
|
) external payable containsCaller(salt) returns (address metamorphicContractAddress) {
|
||||||
|
// move initialization calldata to memory.
|
||||||
|
bytes memory data = metamorphicContractInitializationCalldata;
|
||||||
|
|
||||||
|
// move the initialization code from storage to memory.
|
||||||
|
bytes memory initCode = _metamorphicContractInitializationCode;
|
||||||
|
|
||||||
|
// declare variable to verify successful metamorphic contract deployment.
|
||||||
|
address deployedMetamorphicContract;
|
||||||
|
|
||||||
|
// determine the address of the metamorphic contract.
|
||||||
|
metamorphicContractAddress = _getMetamorphicContractAddress(salt);
|
||||||
|
|
||||||
|
// store the implementation to be retrieved by the metamorphic contract.
|
||||||
|
_implementations[metamorphicContractAddress] = implementationContract;
|
||||||
|
|
||||||
|
// using inline assembly: load data and length of data, then call CREATE2.
|
||||||
|
/* solhint-disable no-inline-assembly */
|
||||||
|
assembly {
|
||||||
|
let encoded_data := add(0x20, initCode) // load initialization code.
|
||||||
|
let encoded_size := mload(initCode) // load the init code's length.
|
||||||
|
deployedMetamorphicContract := create2(
|
||||||
|
// call CREATE2 with 4 arguments.
|
||||||
|
0, // do not forward any endowment.
|
||||||
|
encoded_data, // pass in initialization code.
|
||||||
|
encoded_size, // pass in init code's length.
|
||||||
|
salt // pass in the salt value.
|
||||||
|
)
|
||||||
|
} /* solhint-enable no-inline-assembly */
|
||||||
|
|
||||||
|
// ensure that the contracts were successfully deployed.
|
||||||
|
require(deployedMetamorphicContract == metamorphicContractAddress, "Failed to deploy the new metamorphic contract.");
|
||||||
|
|
||||||
|
// initialize the new metamorphic contract if any data or value is provided.
|
||||||
|
if (data.length > 0 || msg.value > 0) {
|
||||||
|
/* solhint-disable avoid-call-value */
|
||||||
|
(bool success, ) = metamorphicContractAddress.call.value(msg.value)(data);
|
||||||
|
/* solhint-enable avoid-call-value */
|
||||||
|
|
||||||
|
require(success, "Failed to initialize the new metamorphic contract.");
|
||||||
|
}
|
||||||
|
|
||||||
|
emit Metamorphosed(deployedMetamorphicContract, implementationContract);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* solhint-disable function-max-lines */
|
||||||
|
/**
|
||||||
|
* @dev Deploy a metamorphic contract by submitting a given salt or nonce
|
||||||
|
* along with the initialization code to a transient contract which will then
|
||||||
|
* deploy the metamorphic contract before immediately selfdestructing. To
|
||||||
|
* replace the metamorphic contract, first selfdestruct the current contract,
|
||||||
|
* then call with the same salt value and new initialization code (be aware
|
||||||
|
* that all existing state will be wiped from the existing contract). Also
|
||||||
|
* note that the first 20 bytes of the salt must match the calling address,
|
||||||
|
* which prevents contracts from being created by unintended parties.
|
||||||
|
* @param salt bytes32 The nonce that will be passed into the CREATE2 call and
|
||||||
|
* thus will determine the resulant address of the metamorphic contract.
|
||||||
|
* @param initializationCode bytes The initialization code for the metamorphic
|
||||||
|
* contract that will be deployed by the transient contract.
|
||||||
|
* @return Address of the metamorphic contract that will be created.
|
||||||
|
*/
|
||||||
|
function deployMetamorphicContractWithConstructor(bytes32 salt, bytes calldata initializationCode)
|
||||||
|
external
|
||||||
|
payable
|
||||||
|
containsCaller(salt)
|
||||||
|
returns (address metamorphicContractAddress)
|
||||||
|
{
|
||||||
|
// move transient contract initialization code from storage to memory.
|
||||||
|
bytes memory initCode = _transientContractInitializationCode;
|
||||||
|
|
||||||
|
// declare variable to verify successful transient contract deployment.
|
||||||
|
address deployedTransientContract;
|
||||||
|
|
||||||
|
// determine the address of the transient contract.
|
||||||
|
address transientContractAddress = _getTransientContractAddress(salt);
|
||||||
|
|
||||||
|
// store the initialization code to be retrieved by the transient contract.
|
||||||
|
_initCodes[transientContractAddress] = initializationCode;
|
||||||
|
|
||||||
|
// load transient contract data and length of data, then deploy via CREATE2.
|
||||||
|
/* solhint-disable no-inline-assembly */
|
||||||
|
assembly {
|
||||||
|
let encoded_data := add(0x20, initCode) // load initialization code.
|
||||||
|
let encoded_size := mload(initCode) // load the init code's length.
|
||||||
|
deployedTransientContract := create2(
|
||||||
|
// call CREATE2 with 4 arguments.
|
||||||
|
callvalue, // forward any supplied endowment.
|
||||||
|
encoded_data, // pass in initialization code.
|
||||||
|
encoded_size, // pass in init code's length.
|
||||||
|
salt // pass in the salt value.
|
||||||
|
)
|
||||||
|
} /* solhint-enable no-inline-assembly */
|
||||||
|
|
||||||
|
// ensure that the contracts were successfully deployed.
|
||||||
|
require(
|
||||||
|
deployedTransientContract == transientContractAddress,
|
||||||
|
"Failed to deploy metamorphic contract using given salt and init code."
|
||||||
|
);
|
||||||
|
|
||||||
|
metamorphicContractAddress = _getMetamorphicContractAddressWithConstructor(transientContractAddress);
|
||||||
|
|
||||||
|
emit MetamorphosedWithConstructor(metamorphicContractAddress, transientContractAddress);
|
||||||
|
} /* solhint-enable function-max-lines */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev View function for retrieving the address of the implementation
|
||||||
|
* contract to clone. Called by the constructor of each metamorphic contract.
|
||||||
|
*/
|
||||||
|
function getImplementation() external view returns (address implementation) {
|
||||||
|
return _implementations[msg.sender];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev View function for retrieving the initialization code for a given
|
||||||
|
* metamorphic contract to deploy via a transient contract. Called by the
|
||||||
|
* constructor of each transient contract.
|
||||||
|
* @return The initialization code to use to deploy the metamorphic contract.
|
||||||
|
*/
|
||||||
|
function getInitializationCode() external view returns (bytes memory initializationCode) {
|
||||||
|
return _initCodes[msg.sender];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev View function for retrieving the address of the current implementation
|
||||||
|
* contract of a given metamorphic contract, where the address of the contract
|
||||||
|
* is supplied as an argument. Be aware that the implementation contract has
|
||||||
|
* an independent state and may have been altered or selfdestructed from when
|
||||||
|
* it was last cloned by the metamorphic contract.
|
||||||
|
* @param metamorphicContractAddress address The address of the metamorphic
|
||||||
|
* contract.
|
||||||
|
* @return Address of the corresponding implementation contract.
|
||||||
|
*/
|
||||||
|
function getImplementationContractAddress(address metamorphicContractAddress)
|
||||||
|
external
|
||||||
|
view
|
||||||
|
returns (address implementationContractAddress)
|
||||||
|
{
|
||||||
|
return _implementations[metamorphicContractAddress];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev View function for retrieving the initialization code for a given
|
||||||
|
* metamorphic contract instance deployed via a transient contract, where the address
|
||||||
|
* of the transient contract is supplied as an argument.
|
||||||
|
* @param transientContractAddress address The address of the transient
|
||||||
|
* contract that deployed the metamorphic contract.
|
||||||
|
* @return The initialization code used to deploy the metamorphic contract.
|
||||||
|
*/
|
||||||
|
function getMetamorphicContractInstanceInitializationCode(address transientContractAddress)
|
||||||
|
external
|
||||||
|
view
|
||||||
|
returns (bytes memory initializationCode)
|
||||||
|
{
|
||||||
|
return _initCodes[transientContractAddress];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Compute the address of the metamorphic contract that will be created
|
||||||
|
* upon submitting a given salt to the contract.
|
||||||
|
* @param salt bytes32 The nonce passed into CREATE2 by metamorphic contract.
|
||||||
|
* @return Address of the corresponding metamorphic contract.
|
||||||
|
*/
|
||||||
|
function findMetamorphicContractAddress(bytes32 salt) external view returns (address metamorphicContractAddress) {
|
||||||
|
// determine the address where the metamorphic contract will be deployed.
|
||||||
|
metamorphicContractAddress = _getMetamorphicContractAddress(salt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Compute the address of the transient contract that will be created
|
||||||
|
* upon submitting a given salt to the contract.
|
||||||
|
* @param salt bytes32 The nonce passed into CREATE2 when deploying the
|
||||||
|
* transient contract.
|
||||||
|
* @return Address of the corresponding transient contract.
|
||||||
|
*/
|
||||||
|
function findTransientContractAddress(bytes32 salt) external view returns (address transientContractAddress) {
|
||||||
|
// determine the address where the transient contract will be deployed.
|
||||||
|
transientContractAddress = _getTransientContractAddress(salt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Compute the address of the metamorphic contract that will be created
|
||||||
|
* by the transient contract upon submitting a given salt to the contract.
|
||||||
|
* @param salt bytes32 The nonce passed into CREATE2 when deploying the
|
||||||
|
* transient contract.
|
||||||
|
* @return Address of the corresponding metamorphic contract.
|
||||||
|
*/
|
||||||
|
function findMetamorphicContractAddressWithConstructor(bytes32 salt)
|
||||||
|
external
|
||||||
|
view
|
||||||
|
returns (address metamorphicContractAddress)
|
||||||
|
{
|
||||||
|
// determine the address of the metamorphic contract.
|
||||||
|
metamorphicContractAddress = _getMetamorphicContractAddressWithConstructor(_getTransientContractAddress(salt));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev View function for retrieving the initialization code of metamorphic
|
||||||
|
* contracts for purposes of verification.
|
||||||
|
*/
|
||||||
|
function getMetamorphicContractInitializationCode() external view returns (bytes memory metamorphicContractInitializationCode) {
|
||||||
|
return _metamorphicContractInitializationCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev View function for retrieving the keccak256 hash of the initialization
|
||||||
|
* code of metamorphic contracts for purposes of verification.
|
||||||
|
*/
|
||||||
|
function getMetamorphicContractInitializationCodeHash()
|
||||||
|
external
|
||||||
|
view
|
||||||
|
returns (bytes32 metamorphicContractInitializationCodeHash)
|
||||||
|
{
|
||||||
|
return _metamorphicContractInitializationCodeHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev View function for retrieving the initialization code of transient
|
||||||
|
* contracts for purposes of verification.
|
||||||
|
*/
|
||||||
|
function getTransientContractInitializationCode() external view returns (bytes memory transientContractInitializationCode) {
|
||||||
|
return _transientContractInitializationCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev View function for retrieving the keccak256 hash of the initialization
|
||||||
|
* code of transient contracts for purposes of verification.
|
||||||
|
*/
|
||||||
|
function getTransientContractInitializationCodeHash() external view returns (bytes32 transientContractInitializationCodeHash) {
|
||||||
|
return _transientContractInitializationCodeHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Internal view function for calculating a metamorphic contract address
|
||||||
|
* given a particular salt.
|
||||||
|
*/
|
||||||
|
function _getMetamorphicContractAddress(bytes32 salt) internal view returns (address) {
|
||||||
|
// determine the address of the metamorphic contract.
|
||||||
|
return
|
||||||
|
address(
|
||||||
|
uint160( // downcast to match the address type.
|
||||||
|
uint256( // convert to uint to truncate upper digits.
|
||||||
|
keccak256( // compute the CREATE2 hash using 4 inputs.
|
||||||
|
abi.encodePacked( // pack all inputs to the hash together.
|
||||||
|
hex"ff", // start with 0xff to distinguish from RLP.
|
||||||
|
address(this), // this contract will be the caller.
|
||||||
|
salt, // pass in the supplied salt value.
|
||||||
|
_metamorphicContractInitializationCodeHash // the init code hash.
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Internal view function for calculating a transient contract address
|
||||||
|
* given a particular salt.
|
||||||
|
*/
|
||||||
|
function _getTransientContractAddress(bytes32 salt) internal view returns (address) {
|
||||||
|
// determine the address of the transient contract.
|
||||||
|
return
|
||||||
|
address(
|
||||||
|
uint160( // downcast to match the address type.
|
||||||
|
uint256( // convert to uint to truncate upper digits.
|
||||||
|
keccak256( // compute the CREATE2 hash using 4 inputs.
|
||||||
|
abi.encodePacked( // pack all inputs to the hash together.
|
||||||
|
hex"ff", // start with 0xff to distinguish from RLP.
|
||||||
|
address(this), // this contract will be the caller.
|
||||||
|
salt, // pass in the supplied salt value.
|
||||||
|
_transientContractInitializationCodeHash // supply init code hash.
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Internal view function for calculating a metamorphic contract address
|
||||||
|
* that has been deployed via a transient contract given the address of the
|
||||||
|
* transient contract.
|
||||||
|
*/
|
||||||
|
function _getMetamorphicContractAddressWithConstructor(address transientContractAddress) internal pure returns (address) {
|
||||||
|
// determine the address of the metamorphic contract.
|
||||||
|
return
|
||||||
|
address(
|
||||||
|
uint160( // downcast to match the address type.
|
||||||
|
uint256( // set to uint to truncate upper digits.
|
||||||
|
keccak256( // compute CREATE hash via RLP encoding.
|
||||||
|
abi.encodePacked( // pack all inputs to the hash together.
|
||||||
|
bytes1(0xd6), // first RLP byte.
|
||||||
|
bytes1(0x94), // second RLP byte.
|
||||||
|
transientContractAddress, // called by the transient contract.
|
||||||
|
bytes1(0x01) // nonce begins at 1 for contracts.
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Modifier to ensure that the first 20 bytes of a submitted salt match
|
||||||
|
* those of the calling account. This provides protection against the salt
|
||||||
|
* being stolen by frontrunners or other attackers.
|
||||||
|
* @param salt bytes32 The salt value to check against the calling address.
|
||||||
|
*/
|
||||||
|
modifier containsCaller(bytes32 salt) {
|
||||||
|
// prevent contract submissions from being stolen from tx.pool by requiring
|
||||||
|
// that the first 20 bytes of the submitted salt match msg.sender.
|
||||||
|
require(address(bytes20(salt)) == msg.sender, "Invalid salt - first 20 bytes of the salt must match calling address.");
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
}
|
32
contracts/v4-patch/mock/MockProposals.sol
Normal file
32
contracts/v4-patch/mock/MockProposals.sol
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pragma solidity ^0.6.12;
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
|
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||||
|
|
||||||
|
contract InitialProposal {
|
||||||
|
event MockExecuted(uint256 num);
|
||||||
|
|
||||||
|
function executeProposal() external virtual {
|
||||||
|
emit MockExecuted(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function emergencyStop() public {
|
||||||
|
selfdestruct(payable(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contract MaliciousProposal is InitialProposal {
|
||||||
|
address public immutable deployer;
|
||||||
|
|
||||||
|
constructor() public {
|
||||||
|
deployer = msg.sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
function executeProposal() external virtual override {
|
||||||
|
IERC20 torn = IERC20(0x77777FeDdddFfC19Ff86DB637967013e6C6A116C);
|
||||||
|
uint256 bal = torn.balanceOf(address(this));
|
||||||
|
torn.transfer(deployer, bal);
|
||||||
|
}
|
||||||
|
}
|
96
diffs/RelayerRegistry.diff
Normal file
96
diffs/RelayerRegistry.diff
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
10a11
|
||||||
|
> import { ENSNamehash } from "./utils/ENSNamehash.sol";
|
||||||
|
12c13,14
|
||||||
|
< import { TornadoStakingRewards } from "./TornadoStakingRewards.sol";
|
||||||
|
---
|
||||||
|
> import { TornadoStakingRewards } from "./staking/TornadoStakingRewards.sol";
|
||||||
|
> import { IENS } from "./interfaces/IENS.sol";
|
||||||
|
14,77c16,17
|
||||||
|
< interface ITornadoInstance {
|
||||||
|
< function token() external view returns (address);
|
||||||
|
<
|
||||||
|
< function denomination() external view returns (uint256);
|
||||||
|
<
|
||||||
|
< function deposit(bytes32 commitment) external payable;
|
||||||
|
<
|
||||||
|
< function withdraw(
|
||||||
|
< bytes calldata proof,
|
||||||
|
< bytes32 root,
|
||||||
|
< bytes32 nullifierHash,
|
||||||
|
< address payable recipient,
|
||||||
|
< address payable relayer,
|
||||||
|
< uint256 fee,
|
||||||
|
< uint256 refund
|
||||||
|
< ) external payable;
|
||||||
|
< }
|
||||||
|
<
|
||||||
|
< interface IENS {
|
||||||
|
< function owner(bytes32 node) external view returns (address);
|
||||||
|
< }
|
||||||
|
<
|
||||||
|
< /*
|
||||||
|
< * @dev Solidity implementation of the ENS namehash algorithm.
|
||||||
|
< *
|
||||||
|
< * Warning! Does not normalize or validate names before hashing.
|
||||||
|
< * Original version can be found here https://github.com/JonahGroendal/ens-namehash/
|
||||||
|
< */
|
||||||
|
< library ENSNamehash {
|
||||||
|
< function namehash(bytes memory domain) internal pure returns (bytes32) {
|
||||||
|
< return namehash(domain, 0);
|
||||||
|
< }
|
||||||
|
<
|
||||||
|
< function namehash(bytes memory domain, uint256 i) internal pure returns (bytes32) {
|
||||||
|
< if (domain.length <= i) return 0x0000000000000000000000000000000000000000000000000000000000000000;
|
||||||
|
<
|
||||||
|
< uint256 len = labelLength(domain, i);
|
||||||
|
<
|
||||||
|
< return keccak256(abi.encodePacked(namehash(domain, i + len + 1), keccak(domain, i, len)));
|
||||||
|
< }
|
||||||
|
<
|
||||||
|
< function labelLength(bytes memory domain, uint256 i) private pure returns (uint256) {
|
||||||
|
< uint256 len;
|
||||||
|
< while (i + len != domain.length && domain[i + len] != 0x2e) {
|
||||||
|
< len++;
|
||||||
|
< }
|
||||||
|
< return len;
|
||||||
|
< }
|
||||||
|
<
|
||||||
|
< function keccak(
|
||||||
|
< bytes memory data,
|
||||||
|
< uint256 offset,
|
||||||
|
< uint256 len
|
||||||
|
< ) private pure returns (bytes32 ret) {
|
||||||
|
< require(offset + len <= data.length);
|
||||||
|
< assembly {
|
||||||
|
< ret := keccak256(add(add(data, 32), offset), len)
|
||||||
|
< }
|
||||||
|
< }
|
||||||
|
< }
|
||||||
|
<
|
||||||
|
< interface IFeeManager {
|
||||||
|
< function instanceFeeWithUpdate(ITornadoInstance _instance) external returns (uint160);
|
||||||
|
< }
|
||||||
|
---
|
||||||
|
> import "./tornado-proxy/TornadoRouter.sol";
|
||||||
|
> import "./tornado-proxy/FeeManager.sol";
|
||||||
|
106c46
|
||||||
|
< IFeeManager public immutable feeManager;
|
||||||
|
---
|
||||||
|
> FeeManager public immutable feeManager;
|
||||||
|
142,143c82,83
|
||||||
|
< address _staking,
|
||||||
|
< address _feeManager
|
||||||
|
---
|
||||||
|
> bytes32 _staking,
|
||||||
|
> bytes32 _feeManager
|
||||||
|
148,149c88,89
|
||||||
|
< staking = TornadoStakingRewards(_staking);
|
||||||
|
< feeManager = IFeeManager(_feeManager);
|
||||||
|
---
|
||||||
|
> staking = TornadoStakingRewards(resolve(_staking));
|
||||||
|
> feeManager = FeeManager(resolve(_feeManager));
|
||||||
|
384c324
|
||||||
|
< }
|
||||||
|
---
|
||||||
|
> }
|
||||||
|
\ No newline at end of file
|
28
diffs/TornadoStakingRewards.diff
Normal file
28
diffs/TornadoStakingRewards.diff
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
11,20c11
|
||||||
|
<
|
||||||
|
< interface ITornadoVault {
|
||||||
|
< function withdrawTorn(address recipient, uint256 amount) external;
|
||||||
|
< }
|
||||||
|
<
|
||||||
|
< interface ITornadoGovernance {
|
||||||
|
< function lockedBalance(address account) external view returns (uint256);
|
||||||
|
<
|
||||||
|
< function userVault() external view returns (ITornadoVault);
|
||||||
|
< }
|
||||||
|
---
|
||||||
|
> import { ITornadoGovernance } from "../interfaces/ITornadoGovernance.sol";
|
||||||
|
54d44
|
||||||
|
< // Minor code change here we won't resolve the registry by ENS
|
||||||
|
58c48
|
||||||
|
< address _relayerRegistry
|
||||||
|
---
|
||||||
|
> bytes32 _relayerRegistry
|
||||||
|
62c52
|
||||||
|
< relayerRegistry = _relayerRegistry;
|
||||||
|
---
|
||||||
|
> relayerRegistry = resolve(_relayerRegistry);
|
||||||
|
143c133
|
||||||
|
< }
|
||||||
|
---
|
||||||
|
> }
|
||||||
|
\ No newline at end of file
|
@ -25,13 +25,22 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
version: '0.5.6',
|
||||||
|
settings: {
|
||||||
|
optimizer: {
|
||||||
|
enabled: true,
|
||||||
|
runs: 1000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
hardhat: {
|
hardhat: {
|
||||||
forking: {
|
forking: {
|
||||||
url: `${process.env.RPC_URL}`,
|
url: `${process.env.RPC_URL}`,
|
||||||
timeout: 2147483647,
|
timeout: 9999999999,
|
||||||
},
|
},
|
||||||
initialBaseFeePerGas: 5,
|
initialBaseFeePerGas: 5,
|
||||||
},
|
},
|
||||||
@ -42,7 +51,7 @@ module.exports = {
|
|||||||
mainnet: {
|
mainnet: {
|
||||||
url: `${process.env.RPC_URL}`,
|
url: `${process.env.RPC_URL}`,
|
||||||
//accounts: [`${process.env.PK}`],
|
//accounts: [`${process.env.PK}`],
|
||||||
timeout: 2147483647,
|
timeout: 9999999999,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mocha: { timeout: 9999999999 },
|
mocha: { timeout: 9999999999 },
|
||||||
|
@ -1,23 +1,12 @@
|
|||||||
const { expect } = require('chai')
|
const { expect } = require('chai')
|
||||||
const { ethers } = require('hardhat')
|
const { ethers } = require('hardhat')
|
||||||
const { BigNumber } = require('@ethersproject/bignumber')
|
const { BigNumber } = require('@ethersproject/bignumber')
|
||||||
const { propose } = require('../../scripts/helper/propose_proposal.js')
|
|
||||||
|
|
||||||
const config = require('../../config')
|
const config = require('../../config')
|
||||||
|
|
||||||
const { getSignerFromAddress, takeSnapshot, revertSnapshot, advanceTime } = require('../utils')
|
const { takeSnapshot, revertSnapshot } = require('../utils')
|
||||||
|
|
||||||
function printBalanceWithDescription(descr, bn) {
|
describe('Gov Exploit Patch Upgrade Tests', () => {
|
||||||
console.log(
|
|
||||||
`\n ${descr} => ${
|
|
||||||
bn.div(BigNumber.from(10).pow(18)).toString() +
|
|
||||||
'.' +
|
|
||||||
bn.div(BigNumber.from(10).pow(14)).toString().slice(-4)
|
|
||||||
}`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('V3 governance tests', () => {
|
|
||||||
const zero = BigNumber.from(0)
|
const zero = BigNumber.from(0)
|
||||||
|
|
||||||
const ProposalState = {
|
const ProposalState = {
|
||||||
@ -53,20 +42,29 @@ describe('V3 governance tests', () => {
|
|||||||
return _periods
|
return _periods
|
||||||
}
|
}
|
||||||
|
|
||||||
let quorumVotes
|
|
||||||
|
|
||||||
let GovernanceContract
|
|
||||||
let TornToken
|
|
||||||
|
|
||||||
let provider
|
|
||||||
|
|
||||||
let tornwhale
|
let tornwhale
|
||||||
let proposer
|
let proposer
|
||||||
|
|
||||||
let propfacfac
|
let initialProposalDeployer
|
||||||
let propfac
|
let maliciousProposalDeployer
|
||||||
|
|
||||||
|
let initialProposalImpl
|
||||||
|
let maliciousProposalImpl
|
||||||
|
|
||||||
|
let proposalContractsDeployer
|
||||||
|
let proposalDeployer
|
||||||
|
|
||||||
|
let proposerBalanceInitial
|
||||||
|
|
||||||
let torn
|
let torn
|
||||||
let gov
|
let governance
|
||||||
|
let metamorphicFactory
|
||||||
|
|
||||||
|
let exploit = {
|
||||||
|
hacker: undefined,
|
||||||
|
salt: '00000000006578706c6f6974', // hex "exploit", hacker addrs must be prepended
|
||||||
|
address: '0x0000000000000000000000000000000000000000', // Has to be filled
|
||||||
|
}
|
||||||
|
|
||||||
// From other tests
|
// From other tests
|
||||||
|
|
||||||
@ -79,14 +77,6 @@ describe('V3 governance tests', () => {
|
|||||||
await ethers.provider.send('evm_mine', [])
|
await ethers.provider.send('evm_mine', [])
|
||||||
}
|
}
|
||||||
|
|
||||||
let sendr = async (method, params) => {
|
|
||||||
return await ethers.provider.send(method, params)
|
|
||||||
}
|
|
||||||
|
|
||||||
let clog = (...x) => {
|
|
||||||
console.log(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
let pE = (x) => {
|
let pE = (x) => {
|
||||||
return ethers.utils.parseEther(`${x}`)
|
return ethers.utils.parseEther(`${x}`)
|
||||||
}
|
}
|
||||||
@ -97,11 +87,15 @@ describe('V3 governance tests', () => {
|
|||||||
// Pick our signer
|
// Pick our signer
|
||||||
proposer = (await ethers.getSigners())[2]
|
proposer = (await ethers.getSigners())[2]
|
||||||
|
|
||||||
|
// Prepare hacker signer and salt
|
||||||
|
exploit.hacker = (await ethers.getSigners())[3]
|
||||||
|
exploit.salt = exploit.hacker.address + exploit.salt
|
||||||
|
|
||||||
// Ok get current gov
|
// Ok get current gov
|
||||||
gov = (await ethers.getContractAt('GovernanceStakingUpgrade', config.governance)).connect(proposer)
|
governance = (await ethers.getContractAt('GovernanceStakingUpgrade', config.governance)).connect(proposer)
|
||||||
|
|
||||||
// Impersonate
|
// Impersonate
|
||||||
await sendr('hardhat_impersonateAccount', [config.governance])
|
await ethers.provider.send('hardhat_impersonateAccount', [config.governance])
|
||||||
|
|
||||||
// Pick whale
|
// Pick whale
|
||||||
tornwhale = ethers.provider.getSigner(config.governance)
|
tornwhale = ethers.provider.getSigner(config.governance)
|
||||||
@ -113,58 +107,86 @@ describe('V3 governance tests', () => {
|
|||||||
await ethers.provider.send('hardhat_setBalance', [proposer.address, pE(10).toHexString()])
|
await ethers.provider.send('hardhat_setBalance', [proposer.address, pE(10).toHexString()])
|
||||||
|
|
||||||
// Take gov balance
|
// Take gov balance
|
||||||
const govbal = await torn.balanceOf(gov.address)
|
const govbal = await torn.balanceOf(governance.address)
|
||||||
|
|
||||||
// Transfer
|
// Transfer
|
||||||
await torn.transfer(proposer.address, govbal)
|
await torn.transfer(proposer.address, govbal.div(2))
|
||||||
|
|
||||||
|
// Note bal
|
||||||
|
proposerBalanceInitial = await torn.balanceOf(proposer.address)
|
||||||
|
|
||||||
// Check bal was allocated
|
// Check bal was allocated
|
||||||
expect(await torn.balanceOf(proposer.address)).to.equal(govbal)
|
expect(await torn.balanceOf(proposer.address)).to.equal(govbal.div(2))
|
||||||
|
|
||||||
// Connect
|
// Connect
|
||||||
torn = torn.connect(proposer)
|
torn = torn.connect(proposer)
|
||||||
|
|
||||||
periods = await setPeriods(periods, gov)
|
// Allow torn to be locked
|
||||||
|
await torn.approve(governance.address, proposerBalanceInitial)
|
||||||
|
|
||||||
// Factory of the proposal
|
// Lock it
|
||||||
propfacfac = await ethers.getContractFactory('ProposalContractFactory')
|
await governance.connect(proposer).lockWithApproval(proposerBalanceInitial)
|
||||||
propfac = await ethers.getContractFactory('PatchProposal')
|
|
||||||
|
// Get the proposal periods for say executing, voting, and so on
|
||||||
|
periods = await setPeriods(periods, governance)
|
||||||
|
|
||||||
|
// Contracts factories
|
||||||
|
|
||||||
|
initialProposalDeployer = await ethers.getContractFactory('InitialProposal')
|
||||||
|
maliciousProposalDeployer = await ethers.getContractFactory('MaliciousProposal')
|
||||||
|
proposalContractsDeployer = await ethers.getContractFactory('ProposalContractsFactory')
|
||||||
|
proposalDeployer = await ethers.getContractFactory('PatchProposal')
|
||||||
|
|
||||||
|
// Metamorphic & Exploit
|
||||||
|
|
||||||
|
metamorphicFactory = (
|
||||||
|
await ethers.getContractAt('MetamorphicContractFactory', '0x00000000e82eb0431756271F0d00CFB143685e7B')
|
||||||
|
).connect(exploit.hacker)
|
||||||
|
|
||||||
|
initialProposalImpl = await initialProposalDeployer.deploy()
|
||||||
|
maliciousProposalImpl = await maliciousProposalDeployer.deploy()
|
||||||
|
|
||||||
|
exploit.address = await metamorphicFactory.findMetamorphicContractAddress(exploit.salt)
|
||||||
|
|
||||||
|
// Snapshot
|
||||||
|
|
||||||
snapshotId = await takeSnapshot()
|
snapshotId = await takeSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(async () => {
|
describe('Integrative: Patched Governance', () => {
|
||||||
|
after(async () => {
|
||||||
await revertSnapshot(snapshotId)
|
await revertSnapshot(snapshotId)
|
||||||
snapshotId = await takeSnapshot()
|
snapshotId = await takeSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Patch: Main integrative', () => {
|
it('Should be able to execute the proposal', async () => {
|
||||||
it('Should be able to pass all predicates concerning patch update', async () => {
|
// Load these storage variables for comparison
|
||||||
// Load these vars
|
|
||||||
|
|
||||||
const oldVaultAddr = await gov.userVault()
|
const oldVaultAddr = await governance.userVault()
|
||||||
const oldGasCompAddr = await gov.gasCompensationVault()
|
const oldGasCompAddr = await governance.gasCompensationVault()
|
||||||
const oldStaking = await gov.Staking()
|
const oldStaking = await governance.Staking()
|
||||||
|
|
||||||
const govBalBef = await torn.balanceOf(gov.address)
|
|
||||||
const propBal = await torn.balanceOf(proposer.address)
|
|
||||||
|
|
||||||
// Start proposing
|
// Start proposing
|
||||||
|
|
||||||
const proposal = await propfac.deploy((await propfacfac.deploy()).address)
|
const proposal = await proposalDeployer.deploy((await proposalContractsDeployer.deploy()).address)
|
||||||
|
|
||||||
await torn.approve(gov.address, propBal)
|
// Propose
|
||||||
await gov.connect(proposer).lockWithApproval(propBal)
|
|
||||||
|
|
||||||
await gov.propose(proposal.address, 'PATCH')
|
await governance.propose(proposal.address, 'PATCH')
|
||||||
|
|
||||||
const proposalId = await gov.latestProposalIds(proposer.address)
|
// Get the proposal id
|
||||||
|
|
||||||
let proposalData = await gov.proposals(proposalId)
|
const proposalId = await governance.latestProposalIds(proposer.address)
|
||||||
|
|
||||||
|
// Get proposal data
|
||||||
|
|
||||||
|
let proposalData = await governance.proposals(proposalId)
|
||||||
|
|
||||||
|
// Mine up until we can start voting
|
||||||
|
|
||||||
await minewait(periods.VOTING_DELAY.add(1).toNumber())
|
await minewait(periods.VOTING_DELAY.add(1).toNumber())
|
||||||
|
|
||||||
await gov.castVote(proposalId, true)
|
await governance.castVote(proposalId, true)
|
||||||
|
|
||||||
await ethers.provider.send('evm_setNextBlockTimestamp', [
|
await ethers.provider.send('evm_setNextBlockTimestamp', [
|
||||||
proposalData.endTime.add(periods.EXECUTION_DELAY).add(BigNumber.from(1000)).toNumber(),
|
proposalData.endTime.add(periods.EXECUTION_DELAY).add(BigNumber.from(1000)).toNumber(),
|
||||||
@ -172,11 +194,11 @@ describe('V3 governance tests', () => {
|
|||||||
|
|
||||||
await ethers.provider.send('evm_mine', [])
|
await ethers.provider.send('evm_mine', [])
|
||||||
|
|
||||||
await gov.execute(proposalId)
|
await governance.execute(proposalId)
|
||||||
|
|
||||||
const newVaultAddr = await gov.userVault()
|
const newVaultAddr = await governance.userVault()
|
||||||
const newGasCompAddr = await gov.gasCompensationVault()
|
const newGasCompAddr = await governance.gasCompensationVault()
|
||||||
const newStaking = await gov.Staking()
|
const newStaking = await governance.Staking()
|
||||||
|
|
||||||
expect(oldGasCompAddr).to.equal(newGasCompAddr)
|
expect(oldGasCompAddr).to.equal(newGasCompAddr)
|
||||||
expect(newVaultAddr).to.equal(oldVaultAddr)
|
expect(newVaultAddr).to.equal(oldVaultAddr)
|
||||||
@ -184,5 +206,171 @@ describe('V3 governance tests', () => {
|
|||||||
.to.not.equal(oldStaking)
|
.to.not.equal(oldStaking)
|
||||||
.and.to.not.equal('0x0000000000000000000000000000000000000000')
|
.and.to.not.equal('0x0000000000000000000000000000000000000000')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should not be susceptible to the contract metamorphosis exploit', async () => {
|
||||||
|
// First deploy @ metamorphic the valid contract
|
||||||
|
let response = await metamorphicFactory.deployMetamorphicContractFromExistingImplementation(
|
||||||
|
exploit.salt,
|
||||||
|
initialProposalImpl.address,
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
|
const initialProposalAddress = (await response.wait()).events[0].args[0]
|
||||||
|
|
||||||
|
// Must equal
|
||||||
|
expect(initialProposalAddress).to.equal(exploit.address)
|
||||||
|
|
||||||
|
// Load the contract
|
||||||
|
const initialProposal = await ethers.getContractAt('InitialProposal', initialProposalAddress)
|
||||||
|
|
||||||
|
// Propose the valid one
|
||||||
|
await governance.propose(initialProposal.address, 'VALID')
|
||||||
|
|
||||||
|
// Get the proposal id
|
||||||
|
const proposalId = await governance.latestProposalIds(proposer.address)
|
||||||
|
|
||||||
|
// Get proposal data
|
||||||
|
let proposalData = await governance.proposals(proposalId)
|
||||||
|
|
||||||
|
// Mine up until we can start voting
|
||||||
|
await minewait(periods.VOTING_DELAY.add(1).toNumber())
|
||||||
|
|
||||||
|
// Vote for this
|
||||||
|
await governance.castVote(proposalId, true)
|
||||||
|
|
||||||
|
// Prepare time so we can execute
|
||||||
|
|
||||||
|
await ethers.provider.send('evm_setNextBlockTimestamp', [
|
||||||
|
proposalData.endTime.add(periods.EXECUTION_DELAY).add(BigNumber.from(1000)).toNumber(),
|
||||||
|
])
|
||||||
|
|
||||||
|
await ethers.provider.send('evm_mine', [])
|
||||||
|
|
||||||
|
// Since the proposal has now passed, terminate the original contract
|
||||||
|
await initialProposal.emergencyStop()
|
||||||
|
|
||||||
|
// Run metamorphic deployment again
|
||||||
|
response = await metamorphicFactory.deployMetamorphicContractFromExistingImplementation(
|
||||||
|
exploit.salt,
|
||||||
|
maliciousProposalImpl.address,
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
|
const maliciousProposalAddress = (await response.wait()).events[0].args[0]
|
||||||
|
|
||||||
|
// Confirm again
|
||||||
|
expect(maliciousProposalAddress).to.equal(exploit.address)
|
||||||
|
|
||||||
|
// Load the contract
|
||||||
|
const maliciousProposal = await ethers.getContractAt('MaliciousProposal', maliciousProposalAddress)
|
||||||
|
|
||||||
|
// Check that the malicious proposer is the deployer
|
||||||
|
const deployer = await maliciousProposal.deployer()
|
||||||
|
|
||||||
|
// Get bal before
|
||||||
|
const deployerBalanceBefore = await torn.balanceOf(deployer)
|
||||||
|
const governanceBalanceBefore = await torn.balanceOf(governance.address)
|
||||||
|
|
||||||
|
expect(governanceBalanceBefore).to.be.gt(zero)
|
||||||
|
|
||||||
|
// Now execute
|
||||||
|
await governance.execute(proposalId)
|
||||||
|
|
||||||
|
// Check bal after
|
||||||
|
const deployerBalanceAfter = await torn.balanceOf(deployer)
|
||||||
|
const governanceBalanceAfter = await torn.balanceOf(governance.address)
|
||||||
|
|
||||||
|
// Protected
|
||||||
|
expect(deployerBalanceAfter).to.equal(deployerBalanceBefore)
|
||||||
|
expect(governanceBalanceAfter).to.equal(governanceBalanceBefore)
|
||||||
|
|
||||||
|
// Terminate the contract for the next test
|
||||||
|
await maliciousProposal.emergencyStop()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Integrative: Unpatched Governance', () => {
|
||||||
|
after(async () => {
|
||||||
|
await revertSnapshot(snapshotId)
|
||||||
|
snapshotId = await takeSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('The standard contract should be susceptible to the metamorphosis exploit', async () => {
|
||||||
|
// First deploy @ metamorphic the valid contract
|
||||||
|
let response = await metamorphicFactory.deployMetamorphicContractFromExistingImplementation(
|
||||||
|
exploit.salt,
|
||||||
|
initialProposalImpl.address,
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
|
const initialProposalAddress = (await response.wait()).events[0].args[0]
|
||||||
|
|
||||||
|
// Must equal
|
||||||
|
expect(initialProposalAddress).to.equal(exploit.address)
|
||||||
|
|
||||||
|
// Load the contract
|
||||||
|
const initialProposal = await ethers.getContractAt('InitialProposal', initialProposalAddress)
|
||||||
|
|
||||||
|
// Propose the valid one
|
||||||
|
await governance.propose(initialProposal.address, 'VALID')
|
||||||
|
|
||||||
|
// Get the proposal id
|
||||||
|
const proposalId = await governance.latestProposalIds(proposer.address)
|
||||||
|
|
||||||
|
// Get proposal data
|
||||||
|
let proposalData = await governance.proposals(proposalId)
|
||||||
|
|
||||||
|
// Mine up until we can start voting
|
||||||
|
await minewait(periods.VOTING_DELAY.add(1).toNumber())
|
||||||
|
|
||||||
|
// Vote for this
|
||||||
|
await governance.castVote(proposalId, true)
|
||||||
|
|
||||||
|
// Prepare time so we can execute
|
||||||
|
|
||||||
|
await ethers.provider.send('evm_setNextBlockTimestamp', [
|
||||||
|
proposalData.endTime.add(periods.EXECUTION_DELAY).add(BigNumber.from(1000)).toNumber(),
|
||||||
|
])
|
||||||
|
|
||||||
|
await ethers.provider.send('evm_mine', [])
|
||||||
|
|
||||||
|
// Since the proposal has now passed, terminate the original contract
|
||||||
|
await initialProposal.emergencyStop()
|
||||||
|
|
||||||
|
// Run metamorphic deployment again
|
||||||
|
response = await metamorphicFactory.deployMetamorphicContractFromExistingImplementation(
|
||||||
|
exploit.salt,
|
||||||
|
maliciousProposalImpl.address,
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
|
const maliciousProposalAddress = (await response.wait()).events[0].args[0]
|
||||||
|
|
||||||
|
// Confirm again
|
||||||
|
expect(maliciousProposalAddress).to.equal(exploit.address)
|
||||||
|
|
||||||
|
// Load the contract
|
||||||
|
const maliciousProposal = await ethers.getContractAt('MaliciousProposal', maliciousProposalAddress)
|
||||||
|
|
||||||
|
// Check that the malicious proposer is the deployer
|
||||||
|
const deployer = await maliciousProposal.deployer()
|
||||||
|
|
||||||
|
// Get bal before
|
||||||
|
const deployerBalanceBefore = await torn.balanceOf(deployer)
|
||||||
|
const governanceBalanceBefore = await torn.balanceOf(governance.address)
|
||||||
|
|
||||||
|
expect(governanceBalanceBefore).to.be.gt(zero)
|
||||||
|
|
||||||
|
// Now execute
|
||||||
|
await governance.execute(proposalId)
|
||||||
|
|
||||||
|
// Check bal after
|
||||||
|
const deployerBalanceAfter = await torn.balanceOf(deployer)
|
||||||
|
const governanceBalanceAfter = await torn.balanceOf(governance.address)
|
||||||
|
|
||||||
|
// Protected
|
||||||
|
expect(deployerBalanceAfter).to.be.equal(deployerBalanceBefore.add(governanceBalanceBefore))
|
||||||
|
expect(governanceBalanceAfter).to.equal(zero)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user