Start testing contracts

Signed-off-by: AlienTornadosaurusHex <>
This commit is contained in:
AlienTornadosaurusHex 2023-06-10 00:14:20 +00:00
parent a8e09194ec
commit 827e1d6991
13 changed files with 579 additions and 15 deletions

1
.env.example Normal file

@ -0,0 +1 @@
MAINNET_RPC_URL=

4
.gitignore vendored

@ -4,3 +4,7 @@ node_modules/
.env
.gitsigners
.trash
yarn-error.log
yarn.lock
cache_hardhat
artifacts

@ -5,10 +5,11 @@ out = 'out'
libs = ["node_modules", "lib"]
# Compiler
evm_version = 'shanghai'
auto_detect_solc = true
via_ir = true
optimizer = true
optimizer-runs = 1
optimizer = true
via_ir = true
# Network
chain_id = 1
@ -19,6 +20,9 @@ verbosity = 2
# Remappings
remappings = [
'src/=src/',
'base/=src/base/',
'common/=src/common/',
'ds-test/=lib/ds-test/src/',
'solmate/=lib/solmate/src/',
'forge-std/=lib/forge-std/src/',

@ -9,15 +9,7 @@
},
"scripts": {
"postinstall": "chmod +x script/* && ./script/setup.sh",
"build": "chmod +x script/* && ./script/setup.sh",
"prettier": "prettier --write 'src/**/*.sol'",
"prettier:list": "prettier --list-different 'src/**/*.sol'",
"prettier:check": "prettier --check 'src/**/*.sol'",
"solhint": "solhint --config ./.solhint.json 'src/**/*.sol' --fix",
"solhint:check": "solhint --config ./.solhint.json 'src/**/*.sol'",
"lint": "npm run prettier && npm run solhint",
"lint:check": "npm run prettier:check && npm run solhint:check",
"test": "forge test"
"build": "chmod +x script/* && ./script/setup.sh"
},
"devDependencies": {
"prettier": "^2.5.1",

@ -0,0 +1,27 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IGovernance } from "common/interfaces/IGovernance.sol";
import { IGnosisSafe } from "common/interfaces/IGnosisSafe.sol";
import { TornadoAddresses } from "common/TornadoAddresses.sol";
abstract contract TornadoProposal is TornadoAddresses {
function executeProposal() public virtual;
function getMultisig() internal pure returns (IGnosisSafe) {
return IGnosisSafe(getMultisigAddress());
}
function getTornToken() internal pure returns (IERC20) {
return IERC20(getTornTokenAddress());
}
function getGovernance() internal pure returns (IGovernance) {
return IGovernance(getGovernanceProxyAddress());
}
}

@ -0,0 +1,30 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
contract TornadoAddresses {
function getENSAddress() internal pure returns (address) {
return 0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e;
}
function getTornTokenAddress() internal pure returns (address) {
return 0x77777FeDdddFfC19Ff86DB637967013e6C6A116C;
}
function getMultisigAddress() internal pure returns (address) {
return 0xb04E030140b30C27bcdfaafFFA98C57d80eDa7B4;
}
function getGovernanceProxyAddress() internal pure returns (address) {
return 0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce;
}
function getStakingProxyAddress() internal pure returns (address) {
return 0x5B3f656C80E8ddb9ec01Dd9018815576E9238c29;
}
function getRegistryProxyAddress() internal pure returns (address) {
return 0x58E8dCC13BE9780fC42E8723D8EaD4CF46943dF2;
}
}

@ -0,0 +1,85 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
interface IGnosisSafe {
enum Operation {
Call,
DelegateCall
}
function NAME() external view returns (string memory);
function VERSION() external view returns (string memory);
function nonce() external view returns (uint256);
function domainSeparator() external view returns (bytes32);
function signedMessages(bytes32) external view returns (uint256);
function approvedHashes(address, bytes32) external view returns (uint256);
function setup(
address[] calldata _owners,
uint256 _threshold,
address to,
bytes calldata data,
address fallbackHandler,
address paymentToken,
uint256 payment,
address payable paymentReceiver
) external;
function execTransaction(
address to,
uint256 value,
bytes calldata data,
Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address payable refundReceiver,
bytes calldata signatures
) external returns (bool success);
function requiredTxGas(address to, uint256 value, bytes calldata data, Operation operation)
external
returns (uint256);
function approveHash(bytes32 hashToApprove) external;
function signMessage(bytes calldata _data) external;
function isValidSignature(bytes calldata _data, bytes calldata _signature) external returns (bytes4);
function getMessageHash(bytes memory message) external view returns (bytes32);
function encodeTransactionData(
address to,
uint256 value,
bytes memory data,
Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address refundReceiver,
uint256 _nonce
) external view returns (bytes memory);
function getTransactionHash(
address to,
uint256 value,
bytes memory data,
Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address refundReceiver,
uint256 _nonce
) external view returns (bytes32);
}

@ -0,0 +1,56 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
enum ProposalState {
Pending,
Active,
Defeated,
Timelocked,
AwaitingExecution,
Executed,
Expired
}
struct Proposal {
// Creator of the proposal
address proposer;
// target addresses for the call to be made
address target;
// The block at which voting begins
uint256 startTime;
// The block at which voting ends: votes must be cast prior to this block
uint256 endTime;
// Current number of votes in favor of this proposal
uint256 forVotes;
// Current number of votes in opposition to this proposal
uint256 againstVotes;
// Flag marking whether the proposal has been executed
bool executed;
// Flag marking whether the proposal voting time has been extended
// Voting time can be extended once, if the proposal outcome has changed during CLOSING_PERIOD
bool extended;
}
interface IGovernance {
function initialized() external view returns (bool);
function initializing() external view returns (bool);
function EXECUTION_DELAY() external view returns (uint256);
function EXECUTION_EXPIRATION() external view returns (uint256);
function QUORUM_VOTES() external view returns (uint256);
function PROPOSAL_THRESHOLD() external view returns (uint256);
function VOTING_DELAY() external view returns (uint256);
function VOTING_PERIOD() external view returns (uint256);
function CLOSING_PERIOD() external view returns (uint256);
function VOTE_EXTEND_TIME() external view returns (uint256);
function torn() external view returns (address);
function proposals(uint256 index) external view returns (Proposal memory);
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 execute(uint256 proposalId) external payable;
function state(uint256 proposalId) external view returns (ProposalState);
}

@ -73,7 +73,7 @@ library CurveChainedOracles {
bytes4 _selector = _selectors[o];
// Index of the coin in the curve pool for second UINT256 selector
uint8 _coin = _coins[0];
uint8 _coin = _coins[o];
// Check whether the config actually works
if (_selector == PRICE_ORACLE_SELECTOR) {
@ -106,7 +106,8 @@ library CurveChainedOracles {
uint256 priceDivisor = 1e18;
uint256 inverseNumerator = 1e36;
uint256 numOracles = _oracle.length;
// We know that each oracle addition encodes to exactly 26 bytes
uint256 numOracles = _oracle.length / 26;
for (uint256 o = 0; o < numOracles; o++) {
bytes32 chunk;
@ -175,7 +176,7 @@ contract CurveFeeOracle is IFeeOracle {
/* @dev For each instance, a set of data which translates to a set of chained price oracle calls, we call
this data "chainedPriceOracles" because it encodes all data necessary to execute the chain and calc the
price */
mapping(ITornadoInstance => bytes) internal chainedPriceOracles;
mapping(ITornadoInstance => bytes) public chainedPriceOracles;
/* @dev When setting, store the names as a historical record, key is keccak256(bytes) */
mapping(bytes32 => string) public chainedPriceOracleNames;

145
test/OracleTests.sol Normal file

@ -0,0 +1,145 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
// OZ Imports
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
// Tornado imports
import { ITornadoInstance } from "tornado-anonymity-mining/contracts/interfaces/ITornadoInstance.sol";
// STD Imports
import { Test } from "forge-std/Test.sol";
import { console2 } from "forge-std/console2.sol";
// Local imports
import { IGovernance, Proposal } from "common/interfaces/IGovernance.sol";
import { TornadoAddresses } from "common/TornadoAddresses.sol";
import { UniswapV3FeeOracle } from "src/v2/UniswapV3FeeOracle.sol";
import { CurveFeeOracle, ICurvePriceOracle, CurveChainedOracles } from "src/v2/CurveFeeOracle.sol";
import { InstanceData } from "src/v2/InstanceRegistry.sol";
contract OracleTests is Test {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ VARIABLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
address public constant crvUSDUSDCStableswap2Pool = 0x4DEcE678ceceb27446b35C672dC7d61F30bAD69E;
address public constant tricryptoUSDCPool = 0x7F86Bf177Dd4F3494b841a37e810A34dD56c829B;
ITornadoInstance public constant cu10_000 = ITornadoInstance(0x49f173CDAB99a2C3800F1255393DF9B7a17B82Bb);
IERC20 public constant TORN = IERC20(0x77777FeDdddFfC19Ff86DB637967013e6C6A116C);
UniswapV3FeeOracle v3FeeOracle;
CurveFeeOracle feeOracle;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ TESTING ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function setUp() public {
vm.createSelectFork(vm.envString("MAINNET_RPC_URL"));
v3FeeOracle = new UniswapV3FeeOracle(address(this));
v3FeeOracle.setGlobalTornPoolFee(10_000, true);
v3FeeOracle.setGlobalTwapIntervalSeconds(5400);
v3FeeOracle.setGlobalMinObservationCardinality(10);
feeOracle = new CurveFeeOracle(address(this));
feeOracle.setTornOracleIsUniswapV3(false);
feeOracle.setUniswapV3FeeOracle(v3FeeOracle);
}
function test_curveFeeSingleTricrypto() public {
_setCurveFeeSimpleTricryptoOracleForInstance(feeOracle, cu10_000); // CRVUSD 10_000
console2.log(
"\nShould be 30 * (ETH/USD) ------------------------------------------------\n",
uint256(
feeOracle.getFee(
TORN, cu10_000, InstanceData(IERC20(cu10_000.token()), 0, true, true), 30, 10_000
)
),
"\n------------------------------------------------------------------------\n"
);
}
function test_curveFeeChainedTORN() public {
feeOracle.setTornOracleIsUniswapV3(true);
_setCurveFeeChainedOracleForInstance(feeOracle, cu10_000); // CRVUSD 10_000
console2.log(
"\nTORN Fee calculated ------------------------------------------------------\n",
uint256(
feeOracle.getFee(
TORN, cu10_000, InstanceData(IERC20(cu10_000.token()), 0, true, true), 30, 10_000
)
),
"\n------------------------------------------------------------------------\n"
);
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ HELPERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function _setCurveFeeSimpleTricryptoOracleForInstance(
CurveFeeOracle _feeOracle,
ITornadoInstance _instance
) internal {
ICurvePriceOracle[] memory _oracles = new ICurvePriceOracle[](1);
_oracles[0] = ICurvePriceOracle(tricryptoUSDCPool);
bytes4[] memory _selectors = new bytes4[](1);
_selectors[0] = CurveChainedOracles.PRICE_ORACLE_UINT256_SELECTOR;
uint8[] memory _coins = new uint8[](1);
_coins[0] = 1; // ETHER
bool[] memory _invert = new bool[](1);
_invert[0] = true;
_feeOracle.modifyChainedOracleForInstance(
_instance, _oracles, _selectors, _coins, _invert, "ETH/CRVUSD"
);
}
function _setCurveFeeChainedOracleForInstance(CurveFeeOracle _feeOracle, ITornadoInstance _instance)
internal
{
ICurvePriceOracle[] memory _oracles = new ICurvePriceOracle[](2);
_oracles[0] = ICurvePriceOracle(crvUSDUSDCStableswap2Pool);
_oracles[1] = ICurvePriceOracle(tricryptoUSDCPool);
bytes4[] memory _selectors = new bytes4[](2);
_selectors[0] = CurveChainedOracles.PRICE_ORACLE_SELECTOR;
_selectors[1] = CurveChainedOracles.PRICE_ORACLE_UINT256_SELECTOR;
uint8[] memory _coins = new uint8[](2);
_coins[1] = 1;
bool[] memory _invert = new bool[](2);
_invert[0] = false;
_invert[1] = true;
_feeOracle.modifyChainedOracleForInstance(
_instance, _oracles, _selectors, _coins, _invert, "ETH/CRVUSD"
);
}
}

75
test/ProposalTests.sol Normal file

@ -0,0 +1,75 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
// OZ Imports
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
// Local imports
import { InfrastructureUpgradeProposal } from "src/proposals/InfrastructureUpgradeProposal.sol";
import { UniswapV3FeeOracle } from "src/v2/UniswapV3FeeOracle.sol";
import { CurveFeeOracle, ICurvePriceOracle, CurveChainedOracles } from "src/v2/CurveFeeOracle.sol";
import { InstanceRegistry } from "src/v2/InstanceRegistry.sol";
import { FeeOracleManager } from "src/v2/FeeOracleManager.sol";
import { TornadoRouter } from "src/v2/TornadoRouter.sol";
// Test imports
import { TornadoProposalTest } from "./TornadoProposalTest.sol";
contract ProposalTests is TornadoProposalTest {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ VARIABLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
IERC20 public constant TORN = IERC20(0x77777FeDdddFfC19Ff86DB637967013e6C6A116C);
InstanceRegistry implInstanceRegisty;
UniswapV3FeeOracle v3FeeOracle;
FeeOracleManager feeOracleManager;
TornadoRouter router;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ TESTING ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function setUp() public override {
super.setUp();
implInstanceRegisty = new InstanceRegistry(address(governance));
v3FeeOracle = new UniswapV3FeeOracle(address(governance));
feeOracleManager = new FeeOracleManager(address(TORN), address(governance));
router = new TornadoRouter(address(governance));
}
function test_infrastructureUpgradeProposalBasic() public {
// Create proposal
address proposal = address(
new InfrastructureUpgradeProposal(
address(v3FeeOracle),
address(feeOracleManager),
address(implInstanceRegisty),
address(router)
)
);
// Propose
uint256 id = easyPropose(proposal);
// Wait
waitUntilExecutable(id);
// Exec
governance.execute(id);
}
}

@ -0,0 +1,139 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import { IGovernance, Proposal } from "common/interfaces/IGovernance.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Test } from "forge-std/Test.sol";
import { TornadoAddresses } from "common/TornadoAddresses.sol";
contract TornadoProposalTest is Test, TornadoAddresses {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PERMIT ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bytes32 public constant PERMIT_TYPEHASH =
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
bytes32 public constant EIP712_DOMAIN = keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes("TornadoCash")),
keccak256(bytes("1")),
1,
VERIFIER_ADDRESS
)
);
uint16 public constant PERMIT_FUNC_SELECTOR = uint16(0x1901);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ TEST DUMMIES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
address public constant TEST_REAL_ADDRESS_WITH_BALANCE = 0x9Ff3C1Bea9ffB56a78824FE29f457F066257DD58;
address public constant TEST_RELAYER_ADDRESS = 0x30F96AEF199B399B722F8819c9b0723016CEAe6C; // moon-relayer.eth
// (just for testing)
uint256 public constant TEST_PRIVATE_KEY_ONE =
0x66ddbd7cbe4a566df405f6ded0b908c669f88cdb1656380c050e3a457bd21df0;
uint256 public constant TEST_PRIVATE_KEY_TWO =
0xa4c8c98120e77741a87a116074a2df4ddb20d1149069290fd4a3d7ee65c55064;
address public constant TEST_ADDRESS_ONE = 0x118251976c65AFAf291f5255450ddb5b6A4d8B88;
address public constant TEST_ADDRESS_TWO = 0x63aE7d90Eb37ca39FC62dD9991DbEfeE70673a20;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ADDRESSES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
address public constant VERIFIER_ADDRESS = 0x77777FeDdddFfC19Ff86DB637967013e6C6A116C;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GOVERNANCE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
string public constant PROPOSAL_DESCRIPTION = "{title:'Some proposal',description:''}";
uint256 public EXECUTION_DELAY;
uint256 public EXECUTION_EXPIRATION;
uint256 public QUORUM_VOTES;
uint256 public PROPOSAL_THRESHOLD;
uint256 public VOTING_DELAY;
uint256 public VOTING_PERIOD;
uint256 public CLOSING_PERIOD;
uint256 public VOTE_EXTEND_TIME;
IGovernance public immutable governance = IGovernance(getGovernanceProxyAddress());
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ TEST UTILS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function setUp() public virtual {
vm.createSelectFork(vm.envString("MAINNET_RPC_URL"));
_fetchConfiguration();
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ HELPERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function waitUntilExecutable(uint256 proposalId) internal {
uint256 proposalExecutableTime = getProposalExecutableTime(proposalId);
require(block.timestamp < proposalExecutableTime, "Too late to execute proposal");
vm.warp(proposalExecutableTime);
}
function easyPropose(address proposalAddress) public returns (uint256) {
retrieveAndLockBalance(TEST_PRIVATE_KEY_ONE, TEST_ADDRESS_ONE, QUORUM_VOTES);
retrieveAndLockBalance(TEST_PRIVATE_KEY_TWO, TEST_ADDRESS_TWO, 1 ether);
/* ----------PROPOSER------------ */
vm.startPrank(TEST_ADDRESS_ONE);
uint256 proposalId = governance.propose(proposalAddress, PROPOSAL_DESCRIPTION);
// TIME-TRAVEL
vm.warp(block.timestamp + 6 hours);
governance.castVote(proposalId, true);
vm.stopPrank();
/* ------------------------------ */
/* -------------VOTER-------------*/
vm.startPrank(TEST_ADDRESS_TWO);
governance.castVote(proposalId, true);
vm.stopPrank();
/* ------------------------------ */
return proposalId;
}
function retrieveAndLockBalance(uint256, address voter, uint256 amount) internal {
/* ----------GOVERNANCE------- */
vm.startPrank(getGovernanceProxyAddress());
IERC20(getTornTokenAddress()).transfer(voter, amount);
vm.stopPrank();
/* ----------------------------*/
/* ----------VOTER------------ */
vm.startPrank(voter);
IERC20(getTornTokenAddress()).approve(address(governance), amount);
governance.lockWithApproval(amount);
vm.stopPrank();
/* ----------------------------*/
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function getProposalExecutableTime(uint256 proposalId) internal view returns (uint256) {
Proposal memory proposal = IGovernance(getGovernanceProxyAddress()).proposals(proposalId);
return proposal.endTime + EXECUTION_DELAY + 1 hours;
}
function _fetchConfiguration() internal {
EXECUTION_DELAY = governance.EXECUTION_DELAY();
EXECUTION_EXPIRATION = governance.EXECUTION_EXPIRATION();
QUORUM_VOTES = governance.QUORUM_VOTES();
PROPOSAL_THRESHOLD = governance.PROPOSAL_THRESHOLD();
VOTING_DELAY = governance.VOTING_DELAY();
VOTING_PERIOD = governance.VOTING_PERIOD();
CLOSING_PERIOD = governance.CLOSING_PERIOD();
VOTE_EXTEND_TIME = governance.VOTE_EXTEND_TIME();
}
}

@ -360,7 +360,12 @@ prettier-plugin-solidity@^1.0.0-beta.19:
semver "^7.3.8"
solidity-comments-extractor "^0.0.7"
prettier@^2.5.1, prettier@^2.8.3:
prettier@^2.5.1:
version "2.8.8"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
prettier@^2.8.3:
version "2.8.4"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.4.tgz#34dd2595629bfbb79d344ac4a91ff948694463c3"
integrity sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==