start writing deployment script, work on reintroducing tests
Signed-off-by: T-Hax <>
This commit is contained in:
parent
197d593504
commit
a8495d76a4
11
.env.example
11
.env.example
@ -1,5 +1,6 @@
|
|||||||
ETHERSCAN_KEY=ABC123ABC123ABC123ABC123ABC123ABC1
|
ETHERSCAN_KEY=
|
||||||
ALCHEMY_KEY=ABC123ABC123ABC123ABC123ABC123ABC1
|
PRIVATE_KEY=
|
||||||
PRIVATE_KEY=0xabc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1
|
MAINNET_RPC_URL=
|
||||||
INFURA_API_KEY=ABC123ABC123ABC123ABC123ABC123ABC1
|
RINKEBY_RPC_URL=
|
||||||
FORKNET_RPC_URL=https://link-to.fork
|
GOERLI_RPC_URL=
|
||||||
|
SEPOLIA_RPC_URL=
|
@ -17,5 +17,5 @@ module.exports = {
|
|||||||
admin: '0xBAE5aBfa98466Dbe68836763B087f2d189f4D28f', // TODO
|
admin: '0xBAE5aBfa98466Dbe68836763B087f2d189f4D28f', // TODO
|
||||||
WETH: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
|
WETH: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
|
||||||
UniswapV3Factory: '0x1F98431c8aD98523631AE4a59f267346ea31F984',
|
UniswapV3Factory: '0x1F98431c8aD98523631AE4a59f267346ea31F984',
|
||||||
TWAPSlotsMin: 50,
|
TWAPSlotsMin: 80,
|
||||||
}
|
}
|
||||||
|
@ -1,102 +0,0 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
pragma solidity 0.7.6;
|
|
||||||
pragma abicoder v2;
|
|
||||||
|
|
||||||
import "./interfaces/IInstanceRegistry.sol";
|
|
||||||
import "./interfaces/IInstanceFactory.sol";
|
|
||||||
|
|
||||||
contract AddInstanceProposal {
|
|
||||||
IInstanceFactory public immutable instanceFactory;
|
|
||||||
IInstanceRegistry public immutable instanceRegistry;
|
|
||||||
uint24 public immutable uniswapPoolSwappingFee;
|
|
||||||
address public immutable token;
|
|
||||||
|
|
||||||
uint256 public immutable numInstances;
|
|
||||||
uint256 internal immutable denomination0;
|
|
||||||
uint256 internal immutable denomination1;
|
|
||||||
uint256 internal immutable denomination2;
|
|
||||||
uint256 internal immutable denomination3;
|
|
||||||
uint32 internal immutable protocolFee0;
|
|
||||||
uint32 internal immutable protocolFee1;
|
|
||||||
uint32 internal immutable protocolFee2;
|
|
||||||
uint32 internal immutable protocolFee3;
|
|
||||||
|
|
||||||
event AddInstanceForRegistry(address instance, address token, uint256 denomination);
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
address _instanceFactory,
|
|
||||||
address _instanceRegistry,
|
|
||||||
address _token,
|
|
||||||
uint24 _uniswapPoolSwappingFee,
|
|
||||||
uint256[] memory _denominations,
|
|
||||||
uint32[] memory _protocolFees
|
|
||||||
) {
|
|
||||||
instanceFactory = IInstanceFactory(_instanceFactory);
|
|
||||||
instanceRegistry = IInstanceRegistry(_instanceRegistry);
|
|
||||||
token = _token;
|
|
||||||
uniswapPoolSwappingFee = _uniswapPoolSwappingFee;
|
|
||||||
|
|
||||||
require(_denominations.length == _protocolFees.length, "Incorrect denominations/fees length");
|
|
||||||
uint256 _numInstances = _denominations.length;
|
|
||||||
require(_numInstances > 0 && _numInstances <= 4, "incorrect instances number");
|
|
||||||
numInstances = _numInstances;
|
|
||||||
|
|
||||||
denomination0 = _numInstances > 0 ? _denominations[0] : 0;
|
|
||||||
denomination1 = _numInstances > 1 ? _denominations[1] : 0;
|
|
||||||
denomination2 = _numInstances > 2 ? _denominations[2] : 0;
|
|
||||||
denomination3 = _numInstances > 3 ? _denominations[3] : 0;
|
|
||||||
protocolFee0 = _numInstances > 0 ? _protocolFees[0] : 0;
|
|
||||||
protocolFee1 = _numInstances > 1 ? _protocolFees[1] : 0;
|
|
||||||
protocolFee2 = _numInstances > 2 ? _protocolFees[2] : 0;
|
|
||||||
protocolFee3 = _numInstances > 3 ? _protocolFees[3] : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function executeProposal() external {
|
|
||||||
for (uint256 i = 0; i < numInstances; i++) {
|
|
||||||
address instance = instanceFactory.createInstanceClone(denominationByIndex(i), token);
|
|
||||||
|
|
||||||
IInstanceRegistry.Instance memory newInstanceData = IInstanceRegistry.Instance(
|
|
||||||
token != address(0),
|
|
||||||
IERC20(token),
|
|
||||||
IInstanceRegistry.InstanceState.ENABLED,
|
|
||||||
uniswapPoolSwappingFee,
|
|
||||||
protocolFeeByIndex(i)
|
|
||||||
);
|
|
||||||
|
|
||||||
IInstanceRegistry.Tornado memory tornadoForUpdate = IInstanceRegistry.Tornado(instance, newInstanceData);
|
|
||||||
|
|
||||||
instanceRegistry.updateInstance(tornadoForUpdate);
|
|
||||||
|
|
||||||
emit AddInstanceForRegistry(address(instance), token, denominationByIndex(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function denominationByIndex(uint256 _index) public view returns (uint256) {
|
|
||||||
if (_index == 0) {
|
|
||||||
return denomination0;
|
|
||||||
} else if (_index == 1) {
|
|
||||||
return denomination1;
|
|
||||||
} else if (_index == 2) {
|
|
||||||
return denomination2;
|
|
||||||
} else if (_index == 3) {
|
|
||||||
return denomination3;
|
|
||||||
} else {
|
|
||||||
revert("Invalid instance index");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function protocolFeeByIndex(uint256 _index) public view returns (uint32) {
|
|
||||||
if (_index == 0) {
|
|
||||||
return protocolFee0;
|
|
||||||
} else if (_index == 1) {
|
|
||||||
return protocolFee1;
|
|
||||||
} else if (_index == 2) {
|
|
||||||
return protocolFee2;
|
|
||||||
} else if (_index == 3) {
|
|
||||||
return protocolFee3;
|
|
||||||
} else {
|
|
||||||
revert("Invalid instance index");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -40,12 +40,16 @@ contract InstanceAdditionProposal {
|
|||||||
uint256 howMany = toAdd.length;
|
uint256 howMany = toAdd.length;
|
||||||
|
|
||||||
for (uint256 i = 0; i < howMany; i++) {
|
for (uint256 i = 0; i < howMany; i++) {
|
||||||
|
// Read out the data for the new instance
|
||||||
InstanceAdditionData memory data = toAdd[i];
|
InstanceAdditionData memory data = toAdd[i];
|
||||||
|
|
||||||
|
// Safely calculate the denomination
|
||||||
uint256 denomination = uint256(data.smallDenomination).mul(10 ** uint256(data.base10Exponent));
|
uint256 denomination = uint256(data.smallDenomination).mul(10 ** uint256(data.base10Exponent));
|
||||||
|
|
||||||
|
// Create the instance
|
||||||
address instance = instanceFactory.createInstanceClone(denomination, data.tokenAddress);
|
address instance = instanceFactory.createInstanceClone(denomination, data.tokenAddress);
|
||||||
|
|
||||||
|
// Prepare the data for the registry.
|
||||||
IInstanceRegistry.Instance memory newInstanceData = IInstanceRegistry.Instance(
|
IInstanceRegistry.Instance memory newInstanceData = IInstanceRegistry.Instance(
|
||||||
data.tokenAddress != address(0),
|
data.tokenAddress != address(0),
|
||||||
IERC20(data.tokenAddress),
|
IERC20(data.tokenAddress),
|
||||||
@ -54,8 +58,10 @@ contract InstanceAdditionProposal {
|
|||||||
data.protocolFee
|
data.protocolFee
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Convert the data.
|
||||||
IInstanceRegistry.Tornado memory tornadoForUpdate = IInstanceRegistry.Tornado(instance, newInstanceData);
|
IInstanceRegistry.Tornado memory tornadoForUpdate = IInstanceRegistry.Tornado(instance, newInstanceData);
|
||||||
|
|
||||||
|
// Update the instance data in the registry.
|
||||||
instanceRegistry.updateInstance(tornadoForUpdate);
|
instanceRegistry.updateInstance(tornadoForUpdate);
|
||||||
|
|
||||||
emit AddInstanceForRegistry(address(instance), data.tokenAddress, denomination);
|
emit AddInstanceForRegistry(address(instance), data.tokenAddress, denomination);
|
||||||
|
@ -1,157 +0,0 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
pragma solidity 0.7.6;
|
|
||||||
pragma abicoder v2;
|
|
||||||
|
|
||||||
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
|
|
||||||
import "./AddInstanceProposal.sol";
|
|
||||||
import "./interfaces/IInstanceFactory.sol";
|
|
||||||
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
||||||
import { IERC20Permit } from "@openzeppelin/contracts/drafts/IERC20Permit.sol";
|
|
||||||
import { IUniswapV3Factory } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol";
|
|
||||||
import { IUniswapV3PoolState } from "@uniswap/v3-core/contracts/interfaces/pool/IUniswapV3PoolState.sol";
|
|
||||||
import { Initializable } from "@openzeppelin/contracts/proxy/Initializable.sol";
|
|
||||||
|
|
||||||
contract InstanceProposalCreator is Initializable {
|
|
||||||
using Address for address;
|
|
||||||
|
|
||||||
address public immutable governance;
|
|
||||||
address public immutable torn;
|
|
||||||
IInstanceFactory public immutable instanceFactory;
|
|
||||||
address public immutable instanceRegistry;
|
|
||||||
IUniswapV3Factory public immutable UniswapV3Factory;
|
|
||||||
address public immutable WETH;
|
|
||||||
uint16 public TWAPSlotsMin;
|
|
||||||
uint256 public creationFee;
|
|
||||||
|
|
||||||
event NewCreationFeeSet(uint256 newCreationFee);
|
|
||||||
event NewTWAPSlotsMinSet(uint256 newTWAPSlotsMin);
|
|
||||||
event NewGovernanceProposalCreated(address indexed proposal);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev Throws if called by any account other than the Governance.
|
|
||||||
*/
|
|
||||||
modifier onlyGovernance() {
|
|
||||||
require(governance == msg.sender, "IF: caller is not the Governance");
|
|
||||||
_;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
address _governance,
|
|
||||||
address _instanceFactory,
|
|
||||||
address _instanceRegistry,
|
|
||||||
address _torn,
|
|
||||||
address _UniswapV3Factory,
|
|
||||||
address _WETH
|
|
||||||
) {
|
|
||||||
governance = _governance;
|
|
||||||
instanceFactory = IInstanceFactory(_instanceFactory);
|
|
||||||
instanceRegistry = _instanceRegistry;
|
|
||||||
torn = _torn;
|
|
||||||
UniswapV3Factory = IUniswapV3Factory(_UniswapV3Factory);
|
|
||||||
WETH = _WETH;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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(uint16 _TWAPSlotsMin, uint256 _creationFee) external initializer {
|
|
||||||
TWAPSlotsMin = _TWAPSlotsMin;
|
|
||||||
creationFee = _creationFee;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev Creates AddInstanceProposal with approve.
|
|
||||||
* @param _token address of ERC20 token for a new instance
|
|
||||||
* @param _uniswapPoolSwappingFee fee value of Uniswap instance which will be used for `TORN/token` price determination.
|
|
||||||
* `3000` means 0.3% fee Uniswap pool.
|
|
||||||
* @param _denominations list of denominations for each new instance
|
|
||||||
* @param _protocolFees list of protocol fees for each new instance.
|
|
||||||
* `100` means that instance withdrawal fee is 1% of denomination.
|
|
||||||
*/
|
|
||||||
function createProposalApprove(
|
|
||||||
address _token,
|
|
||||||
uint24 _uniswapPoolSwappingFee,
|
|
||||||
uint256[] memory _denominations,
|
|
||||||
uint32[] memory _protocolFees
|
|
||||||
) external returns (address) {
|
|
||||||
require(IERC20(torn).transferFrom(msg.sender, governance, creationFee));
|
|
||||||
return _createProposal(_token, _uniswapPoolSwappingFee, _denominations, _protocolFees);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev Creates AddInstanceProposal with permit.
|
|
||||||
* @param _token address of ERC20 token for a new instance
|
|
||||||
* @param _uniswapPoolSwappingFee fee value of Uniswap instance which will be used for `TORN/token` price determination.
|
|
||||||
* `3000` means 0.3% fee Uniswap pool.
|
|
||||||
* @param _denominations list of denominations for each new instance
|
|
||||||
* @param _protocolFees list of protocol fees for each new instance.
|
|
||||||
* `100` means that instance withdrawal fee is 1% of denomination.
|
|
||||||
*/
|
|
||||||
function createProposalPermit(
|
|
||||||
address _token,
|
|
||||||
uint24 _uniswapPoolSwappingFee,
|
|
||||||
uint256[] memory _denominations,
|
|
||||||
uint32[] memory _protocolFees,
|
|
||||||
address creater,
|
|
||||||
uint256 deadline,
|
|
||||||
uint8 v,
|
|
||||||
bytes32 r,
|
|
||||||
bytes32 s
|
|
||||||
) external returns (address) {
|
|
||||||
IERC20Permit(torn).permit(creater, address(this), creationFee, deadline, v, r, s);
|
|
||||||
require(IERC20(torn).transferFrom(creater, governance, creationFee));
|
|
||||||
return _createProposal(_token, _uniswapPoolSwappingFee, _denominations, _protocolFees);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _createProposal(
|
|
||||||
address _token,
|
|
||||||
uint24 _uniswapPoolSwappingFee,
|
|
||||||
uint256[] memory _denominations,
|
|
||||||
uint32[] memory _protocolFees
|
|
||||||
) internal returns (address) {
|
|
||||||
require(_token == address(0) || _token.isContract(), "Token is not contract");
|
|
||||||
require(_denominations.length > 0, "Empty denominations");
|
|
||||||
require(_denominations.length == _protocolFees.length, "Incorrect denominations/fees length");
|
|
||||||
|
|
||||||
// check Uniswap Pool
|
|
||||||
for (uint8 i = 0; i < _protocolFees.length; i++) {
|
|
||||||
require(_protocolFees[i] <= 10000, "Protocol fee is more than 100%");
|
|
||||||
if (_protocolFees[i] > 0 && _token != address(0)) {
|
|
||||||
// pool exists
|
|
||||||
address poolAddr = UniswapV3Factory.getPool(_token, WETH, _uniswapPoolSwappingFee);
|
|
||||||
require(poolAddr != address(0), "Uniswap pool is not exist");
|
|
||||||
// TWAP slots
|
|
||||||
(, , , , uint16 observationCardinalityNext, , ) = IUniswapV3PoolState(poolAddr).slot0();
|
|
||||||
require(observationCardinalityNext >= TWAPSlotsMin, "Uniswap pool TWAP slots number is low");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
address proposal = address(
|
|
||||||
new AddInstanceProposal(
|
|
||||||
address(instanceFactory),
|
|
||||||
instanceRegistry,
|
|
||||||
_token,
|
|
||||||
_uniswapPoolSwappingFee,
|
|
||||||
_denominations,
|
|
||||||
_protocolFees
|
|
||||||
)
|
|
||||||
);
|
|
||||||
emit NewGovernanceProposalCreated(proposal);
|
|
||||||
|
|
||||||
return proposal;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setCreationFee(uint256 _creationFee) external onlyGovernance {
|
|
||||||
creationFee = _creationFee;
|
|
||||||
emit NewCreationFeeSet(_creationFee);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setTWAPSlotsMin(uint16 _TWAPSlotsMin) external onlyGovernance {
|
|
||||||
TWAPSlotsMin = _TWAPSlotsMin;
|
|
||||||
emit NewTWAPSlotsMinSet(_TWAPSlotsMin);
|
|
||||||
}
|
|
||||||
}
|
|
@ -105,7 +105,7 @@ contract InstanceProposalFactory {
|
|||||||
|
|
||||||
uint24 swappingFee = _uniswapPoolSwappingFees[t];
|
uint24 swappingFee = _uniswapPoolSwappingFees[t];
|
||||||
|
|
||||||
// 3️⃣ Fails if the token is not a contract or if there is no pool.
|
// 3️⃣ If not eth the token is a contract, the swapping fee must give a good pool, the cardinality must be good.
|
||||||
|
|
||||||
if (token != address(0)) {
|
if (token != address(0)) {
|
||||||
require(token.isContract(), "Token must be a contract if not addr(0)");
|
require(token.isContract(), "Token must be a contract if not addr(0)");
|
||||||
@ -119,7 +119,7 @@ contract InstanceProposalFactory {
|
|||||||
require(minObservationCardinality <= observationCardinalityNext, "Uniswap Pool observation cardinality is low.");
|
require(minObservationCardinality <= observationCardinalityNext, "Uniswap Pool observation cardinality is low.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4️⃣ Denominations, div exponent and protocol fees must have an element at t(oken).
|
// 4️⃣ Denominations, div exponent and protocol fees must have an element at t(oken). The exponent must be good.
|
||||||
|
|
||||||
uint256[] memory denominations = _denominations[t];
|
uint256[] memory denominations = _denominations[t];
|
||||||
|
|
||||||
|
@ -43,8 +43,9 @@ module.exports = {
|
|||||||
networks: {
|
networks: {
|
||||||
hardhat: {
|
hardhat: {
|
||||||
forking: {
|
forking: {
|
||||||
url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_KEY}`,
|
url: process.env.MAINNET_RPC_URL,
|
||||||
blockNumber: 14250000,
|
blockNumber: 14250000,
|
||||||
|
httpHeaders: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; rv:102.0) Gecko/20100101 Firefox/102.0' },
|
||||||
},
|
},
|
||||||
chainId: 1,
|
chainId: 1,
|
||||||
initialBaseFeePerGas: 5,
|
initialBaseFeePerGas: 5,
|
||||||
@ -53,33 +54,46 @@ module.exports = {
|
|||||||
blockGasLimit: 50000000,
|
blockGasLimit: 50000000,
|
||||||
},
|
},
|
||||||
rinkeby: {
|
rinkeby: {
|
||||||
url: `https://rinkeby.infura.io/v3/${process.env.INFURA_API_KEY}`,
|
url: process.env.RINKEBY_RPC_URL,
|
||||||
accounts: process.env.PRIVATE_KEY
|
accounts: process.env.PRIVATE_KEY
|
||||||
? [process.env.PRIVATE_KEY]
|
? [process.env.PRIVATE_KEY]
|
||||||
: { mnemonic: 'test test test test test junk' },
|
: { mnemonic: 'test test test test test junk' },
|
||||||
|
httpHeaders: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; rv:102.0) Gecko/20100101 Firefox/102.0' },
|
||||||
},
|
},
|
||||||
goerli: {
|
goerli: {
|
||||||
url: `https://goerli.infura.io/v3/${process.env.INFURA_API_KEY}`,
|
url: process.env.GOERLI_RPC_URL,
|
||||||
accounts: process.env.PRIVATE_KEY
|
accounts: process.env.PRIVATE_KEY
|
||||||
? [process.env.PRIVATE_KEY]
|
? [process.env.PRIVATE_KEY]
|
||||||
: { mnemonic: 'test test test test test junk' },
|
: { mnemonic: 'test test test test test junk' },
|
||||||
|
httpHeaders: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; rv:102.0) Gecko/20100101 Firefox/102.0' },
|
||||||
|
},
|
||||||
|
sepolia: {
|
||||||
|
url: process.env.SEPOLIA_RPC_URL,
|
||||||
|
accounts: process.env.PRIVATE_KEY
|
||||||
|
? [process.env.PRIVATE_KEY]
|
||||||
|
: { mnemonic: 'test test test test test junk' },
|
||||||
|
httpHeaders: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; rv:102.0) Gecko/20100101 Firefox/102.0' },
|
||||||
},
|
},
|
||||||
mainnet: {
|
mainnet: {
|
||||||
url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_KEY}`,
|
url: process.env.MAINNET_RPC_URL,
|
||||||
accounts: process.env.PRIVATE_KEY
|
accounts: process.env.PRIVATE_KEY
|
||||||
? [process.env.PRIVATE_KEY]
|
? [process.env.PRIVATE_KEY]
|
||||||
: { mnemonic: 'test test test test test junk' },
|
: { mnemonic: 'test test test test test junk' },
|
||||||
|
httpHeaders: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; rv:102.0) Gecko/20100101 Firefox/102.0' },
|
||||||
},
|
},
|
||||||
...(typeof process.env.FORKNET_RPC_URL === 'string' && {
|
// What the fuck is this
|
||||||
forknet: {
|
// ...(typeof process.env.FORKNET_RPC_URL === 'string' && {
|
||||||
url: process.env.FORKNET_RPC_URL,
|
// forknet: {
|
||||||
accounts: process.env.PRIVATE_KEY
|
// url: process.env.FORKNET_RPC_URL,
|
||||||
? [process.env.PRIVATE_KEY]
|
// accounts: process.env.PRIVATE_KEY
|
||||||
: { mnemonic: 'test test test test test junk' },
|
// ? [process.env.PRIVATE_KEY]
|
||||||
},
|
// : { mnemonic: 'test test test test test junk' },
|
||||||
}),
|
// },
|
||||||
|
// }),
|
||||||
|
},
|
||||||
|
mocha: {
|
||||||
|
timeout: 9999999999 /* The following simply does not work: ignore: ["factory.with.registry.test.js", "sidechain.instance.factory.test.js"] */,
|
||||||
},
|
},
|
||||||
mocha: { timeout: 9999999999 },
|
|
||||||
spdxLicenseIdentifier: {
|
spdxLicenseIdentifier: {
|
||||||
overwrite: true,
|
overwrite: true,
|
||||||
runOnCompile: true,
|
runOnCompile: true,
|
||||||
|
83
scripts/deploy.js
Normal file
83
scripts/deploy.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
require('dotenv').config()
|
||||||
|
|
||||||
|
const { ethers } = require('hardhat')
|
||||||
|
const config = require('../config')
|
||||||
|
const { createInterface } = require('readline')
|
||||||
|
|
||||||
|
function idToNetwork(id) {
|
||||||
|
switch (id) {
|
||||||
|
case 1:
|
||||||
|
return 'Mainnet'
|
||||||
|
case 3:
|
||||||
|
return 'Ropsten'
|
||||||
|
case 4:
|
||||||
|
return 'Rinkeby'
|
||||||
|
case 5:
|
||||||
|
return 'Goerli'
|
||||||
|
case 11155111:
|
||||||
|
return 'Sepolia'
|
||||||
|
default:
|
||||||
|
throw Error('\nChain id could not be recognized. What network are you using?\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const prompter = createInterface({ input: process.stdin, output: process.stdout })
|
||||||
|
|
||||||
|
function _prompt(prompt, resolve) {
|
||||||
|
prompter.question(prompt, (answer) => {
|
||||||
|
if (answer == 'y') {
|
||||||
|
userInput.close()
|
||||||
|
resolve(true)
|
||||||
|
} else if (answer == 'n') {
|
||||||
|
userInput.close()
|
||||||
|
resolve(false)
|
||||||
|
} else wipeCachePrompt('', resolve)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function prompt(prompt) {
|
||||||
|
return new Promise((resolve) => _prompt(prompt, resolve))
|
||||||
|
}
|
||||||
|
|
||||||
|
function happyDeployedMessage(name, chainId, address) {
|
||||||
|
return `\n${name} successfully deployed on ${idToNetwork(chainId)} at ${address} 🥳\n`
|
||||||
|
}
|
||||||
|
|
||||||
|
function happyVerifiedMessage(name) {
|
||||||
|
return `\n${name} successfully verified on Etherscan!`
|
||||||
|
}
|
||||||
|
|
||||||
|
const promptMessageBase = (middle) => `\n${middle}\n\nAre you sure you would like to continue? 🧐 (y/n): `
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const minFacFac = await ethers.getContractFactory('MinimalInstanceFactory')
|
||||||
|
const proposalFacFac = await ethers.getContractFactory('InstanceProposalFactory')
|
||||||
|
|
||||||
|
const minFac = await minFacFac.deploy(config.verifier, config.hasher, config.merkleTreeHeight)
|
||||||
|
|
||||||
|
console.log(happyDeployedMessage('MinimalInstanceFactory', minFac.address))
|
||||||
|
|
||||||
|
if (await prompt(promptMessageBase('Continuing to InstanceProposalFactory deployment.'))) {
|
||||||
|
const proposalFac = await proposalFacFac.deploy(
|
||||||
|
config.governance,
|
||||||
|
minFac.address,
|
||||||
|
config.instanceRegistry,
|
||||||
|
config.UniswapV3Factory,
|
||||||
|
config.WETH,
|
||||||
|
config.TWAPSlotsMin,
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log(happyDeployedMessage('InstanceProposalFactory', proposalFac.address))
|
||||||
|
} else {
|
||||||
|
return '\nDecided to stop at InstanceProposalFactory deployment.\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await prompt(promptMessageBase('Continuing to contract verification.'))) {
|
||||||
|
} else {
|
||||||
|
return '\nDecided to stop at contract verification.\n'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main().then((res) => {
|
||||||
|
console.log(res ?? '\nScript succesfully finished.')
|
||||||
|
})
|
@ -1,114 +0,0 @@
|
|||||||
const { ethers } = require('hardhat')
|
|
||||||
const defaultConfig = require('../config')
|
|
||||||
|
|
||||||
async function upgradableContract({ contractName, implConstructorArgs, proxyConstructorArgs, salt }) {
|
|
||||||
const Implementation = await ethers.getContractFactory(contractName)
|
|
||||||
|
|
||||||
const implementationBytecode =
|
|
||||||
Implementation.bytecode + Implementation.interface.encodeDeploy(implConstructorArgs).slice(2)
|
|
||||||
|
|
||||||
const implementationAddress = ethers.utils.getCreate2Address(
|
|
||||||
defaultConfig.singletonFactory,
|
|
||||||
salt,
|
|
||||||
ethers.utils.keccak256(implementationBytecode),
|
|
||||||
)
|
|
||||||
|
|
||||||
const AdminUpgradeableProxy = await ethers.getContractFactory('AdminUpgradeableProxy')
|
|
||||||
const proxyConst = [implementationAddress, ...proxyConstructorArgs]
|
|
||||||
const proxyBytecode =
|
|
||||||
AdminUpgradeableProxy.bytecode + AdminUpgradeableProxy.interface.encodeDeploy(proxyConst).slice(2)
|
|
||||||
|
|
||||||
const proxyAddress = ethers.utils.getCreate2Address(
|
|
||||||
defaultConfig.singletonFactory,
|
|
||||||
salt,
|
|
||||||
ethers.utils.keccak256(proxyBytecode),
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
implementation: {
|
|
||||||
address: implementationAddress,
|
|
||||||
bytecode: implementationBytecode,
|
|
||||||
args: implConstructorArgs,
|
|
||||||
},
|
|
||||||
proxy: { address: proxyAddress, bytecode: proxyBytecode, args: proxyConst },
|
|
||||||
isProxy: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function generate(config = defaultConfig) {
|
|
||||||
// sidechain factory contract -------------------------------------
|
|
||||||
const SidechainFactory = await ethers.getContractFactory('SidechainInstanceFactory')
|
|
||||||
const SidechainFactoryInitData = SidechainFactory.interface.encodeFunctionData('initialize', [
|
|
||||||
config.verifier,
|
|
||||||
config.hasher,
|
|
||||||
config.merkleTreeHeight,
|
|
||||||
config.admin,
|
|
||||||
])
|
|
||||||
|
|
||||||
const sidechainFactory = await upgradableContract({
|
|
||||||
contractName: 'SidechainInstanceFactory',
|
|
||||||
implConstructorArgs: [],
|
|
||||||
proxyConstructorArgs: [config.admin, SidechainFactoryInitData],
|
|
||||||
salt: config.salt,
|
|
||||||
})
|
|
||||||
|
|
||||||
// factory with registry contract ---------------------------------
|
|
||||||
const Factory = await ethers.getContractFactory('InstanceFactory')
|
|
||||||
const FactoryInitData = Factory.interface.encodeFunctionData('initialize', [
|
|
||||||
config.verifier,
|
|
||||||
config.hasher,
|
|
||||||
config.merkleTreeHeight,
|
|
||||||
config.governance,
|
|
||||||
])
|
|
||||||
|
|
||||||
const factory = await upgradableContract({
|
|
||||||
contractName: 'InstanceFactory',
|
|
||||||
implConstructorArgs: [],
|
|
||||||
proxyConstructorArgs: [config.governance, FactoryInitData],
|
|
||||||
salt: config.salt,
|
|
||||||
})
|
|
||||||
|
|
||||||
const ProposalCreator = await ethers.getContractFactory('InstanceProposalCreator')
|
|
||||||
const ProposalCreatorInitData = ProposalCreator.interface.encodeFunctionData('initialize', [
|
|
||||||
config.TWAPSlotsMin,
|
|
||||||
config.creationFee,
|
|
||||||
])
|
|
||||||
|
|
||||||
const proposalCreator = await upgradableContract({
|
|
||||||
contractName: 'InstanceProposalCreator',
|
|
||||||
implConstructorArgs: [
|
|
||||||
config.governance,
|
|
||||||
factory.proxy.address,
|
|
||||||
config.instanceRegistry,
|
|
||||||
config.TORN,
|
|
||||||
config.UniswapV3Factory,
|
|
||||||
config.WETH,
|
|
||||||
],
|
|
||||||
proxyConstructorArgs: [config.governance, ProposalCreatorInitData],
|
|
||||||
salt: config.salt,
|
|
||||||
})
|
|
||||||
|
|
||||||
const result = {
|
|
||||||
sidechainFactory,
|
|
||||||
factory,
|
|
||||||
proposalCreator,
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
async function generateWithLog() {
|
|
||||||
const contracts = await generate()
|
|
||||||
console.log('SidechainInstanceFactory contract: ', contracts.sidechainFactory.implementation.address)
|
|
||||||
console.log('SidechainInstanceFactory proxy contract: ', contracts.sidechainFactory.proxy.address)
|
|
||||||
console.log('Instance factory contract: ', contracts.factory.implementation.address)
|
|
||||||
console.log('Instance factory proxy contract: ', contracts.factory.proxy.address)
|
|
||||||
console.log('Proposal creator contract: ', contracts.proposalCreator.implementation.address)
|
|
||||||
console.log('Proposal creator proxy contract: ', contracts.proposalCreator.proxy.address)
|
|
||||||
return contracts
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
generate,
|
|
||||||
generateWithLog,
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
const { EIP712Signer } = require('@ticket721/e712')
|
|
||||||
|
|
||||||
const Permit = [
|
|
||||||
{ name: 'owner', type: 'address' },
|
|
||||||
{ name: 'spender', type: 'address' },
|
|
||||||
{ name: 'value', type: 'uint256' },
|
|
||||||
{ name: 'nonce', type: 'uint256' },
|
|
||||||
{ name: 'deadline', type: 'uint256' },
|
|
||||||
]
|
|
||||||
|
|
||||||
class PermitSigner extends EIP712Signer {
|
|
||||||
constructor(_domain, _permitArgs) {
|
|
||||||
super(_domain, ['Permit', Permit])
|
|
||||||
this.permitArgs = _permitArgs
|
|
||||||
}
|
|
||||||
|
|
||||||
// Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)
|
|
||||||
setPermitInfo(_permitArgs) {
|
|
||||||
this.permitArgs = _permitArgs
|
|
||||||
}
|
|
||||||
|
|
||||||
getPayload() {
|
|
||||||
return this.generatePayload(this.permitArgs, 'Permit')
|
|
||||||
}
|
|
||||||
|
|
||||||
async getSignature(privateKey) {
|
|
||||||
let payload = this.getPayload()
|
|
||||||
payload.message.owner = payload.message.owner.address
|
|
||||||
const { hex, v, r, s } = await this.sign(privateKey, payload)
|
|
||||||
return {
|
|
||||||
hex,
|
|
||||||
v,
|
|
||||||
r: '0x' + r,
|
|
||||||
s: '0x' + s,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getSignerAddress(permitArgs, signature) {
|
|
||||||
const original_payload = this.generatePayload(permitArgs, 'Permit')
|
|
||||||
return this.verify(original_payload, signature)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { PermitSigner }
|
|
@ -1,883 +0,0 @@
|
|||||||
const hre = require('hardhat')
|
|
||||||
const { ethers, waffle } = hre
|
|
||||||
const { loadFixture } = waffle
|
|
||||||
const { expect } = require('chai')
|
|
||||||
const { BigNumber } = require('@ethersproject/bignumber')
|
|
||||||
const config = require('../config')
|
|
||||||
const { getSignerFromAddress, minewait } = require('./utils')
|
|
||||||
const { PermitSigner } = require('../src/permit.js')
|
|
||||||
const { generate } = require('../src/generateAddresses')
|
|
||||||
const { rbigint, createDeposit, toHex, generateProof, initialize } = require('tornado-cli')
|
|
||||||
|
|
||||||
describe('Instance Factory With Registry Tests', () => {
|
|
||||||
const ProposalState = {
|
|
||||||
Pending: 0,
|
|
||||||
Active: 1,
|
|
||||||
Defeated: 2,
|
|
||||||
Timelocked: 3,
|
|
||||||
AwaitingExecution: 4,
|
|
||||||
Executed: 5,
|
|
||||||
Expired: 6,
|
|
||||||
}
|
|
||||||
|
|
||||||
const addressZero = ethers.constants.AddressZero
|
|
||||||
|
|
||||||
async function fixture() {
|
|
||||||
const [sender, deployer, multisig] = await ethers.getSigners()
|
|
||||||
|
|
||||||
const tornWhale = await getSignerFromAddress(config.tornWhale)
|
|
||||||
|
|
||||||
const compWhale = await getSignerFromAddress(config.compWhale)
|
|
||||||
|
|
||||||
const gov = await ethers.getContractAt('Governance', config.governance)
|
|
||||||
|
|
||||||
const router = await ethers.getContractAt(
|
|
||||||
'tornado-relayer-registry/contracts/tornado-proxy/TornadoRouter.sol:TornadoRouter',
|
|
||||||
config.router,
|
|
||||||
)
|
|
||||||
|
|
||||||
const tornToken = await ethers.getContractAt(
|
|
||||||
'@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20',
|
|
||||||
config.TORN,
|
|
||||||
)
|
|
||||||
|
|
||||||
const compToken = await ethers.getContractAt(
|
|
||||||
'@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20',
|
|
||||||
config.COMP,
|
|
||||||
)
|
|
||||||
|
|
||||||
const instanceRegistry = await ethers.getContractAt(
|
|
||||||
'tornado-relayer-registry/contracts/tornado-proxy/InstanceRegistry.sol:InstanceRegistry',
|
|
||||||
config.instanceRegistry,
|
|
||||||
)
|
|
||||||
|
|
||||||
// deploy InstanceProposalCreator with CREATE2
|
|
||||||
const singletonFactory = await ethers.getContractAt(
|
|
||||||
'SingletonFactory',
|
|
||||||
config.singletonFactoryVerboseWrapper,
|
|
||||||
)
|
|
||||||
const contracts = await generate()
|
|
||||||
if ((await ethers.provider.getCode(contracts.factory.implementation.address)) == '0x') {
|
|
||||||
await singletonFactory.deploy(contracts.factory.implementation.bytecode, config.salt, {
|
|
||||||
gasLimit: config.deployGasLimit,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if ((await ethers.provider.getCode(contracts.factory.proxy.address)) == '0x') {
|
|
||||||
await singletonFactory.deploy(contracts.factory.proxy.bytecode, config.salt, {
|
|
||||||
gasLimit: config.deployGasLimit,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const instanceFactory = await ethers.getContractAt('InstanceFactory', contracts.factory.proxy.address)
|
|
||||||
|
|
||||||
if ((await ethers.provider.getCode(contracts.proposalCreator.implementation.address)) == '0x') {
|
|
||||||
await singletonFactory.deploy(contracts.proposalCreator.implementation.bytecode, config.salt, {
|
|
||||||
gasLimit: config.deployGasLimit,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if ((await ethers.provider.getCode(contracts.proposalCreator.proxy.address)) == '0x') {
|
|
||||||
await singletonFactory.deploy(contracts.proposalCreator.proxy.bytecode, config.salt, {
|
|
||||||
gasLimit: config.deployGasLimit,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const proposalCreator = await ethers.getContractAt(
|
|
||||||
'InstanceProposalCreator',
|
|
||||||
contracts.proposalCreator.proxy.address,
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
sender,
|
|
||||||
deployer,
|
|
||||||
multisig,
|
|
||||||
tornWhale,
|
|
||||||
compWhale,
|
|
||||||
router,
|
|
||||||
gov,
|
|
||||||
tornToken,
|
|
||||||
compToken,
|
|
||||||
instanceRegistry,
|
|
||||||
instanceFactory,
|
|
||||||
proposalCreator,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
it('Should have initialized all successfully', async function () {
|
|
||||||
const { sender, gov, tornToken, instanceRegistry, instanceFactory, proposalCreator } = await loadFixture(
|
|
||||||
fixture,
|
|
||||||
)
|
|
||||||
expect(sender.address).to.exist
|
|
||||||
expect(gov.address).to.exist
|
|
||||||
expect(tornToken.address).to.exist
|
|
||||||
expect(instanceRegistry.address).to.exist
|
|
||||||
expect(instanceFactory.address).to.exist
|
|
||||||
expect(proposalCreator.address).to.exist
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should set correct params for factory', async function () {
|
|
||||||
const { instanceFactory, proposalCreator } = await loadFixture(fixture)
|
|
||||||
|
|
||||||
expect(await proposalCreator.governance()).to.be.equal(config.governance)
|
|
||||||
expect(await instanceFactory.verifier()).to.be.equal(config.verifier)
|
|
||||||
expect(await instanceFactory.hasher()).to.be.equal(config.hasher)
|
|
||||||
expect(await instanceFactory.merkleTreeHeight()).to.be.equal(config.merkleTreeHeight)
|
|
||||||
expect(await instanceFactory.ERC20Impl()).to.exist
|
|
||||||
expect(await instanceFactory.nativeCurImpl()).to.exist
|
|
||||||
expect(await proposalCreator.creationFee()).to.be.equal(config.creationFee)
|
|
||||||
expect(await proposalCreator.torn()).to.be.equal(config.TORN)
|
|
||||||
expect(await proposalCreator.TWAPSlotsMin()).to.be.equal(config.TWAPSlotsMin)
|
|
||||||
expect(await proposalCreator.WETH()).to.be.equal(config.WETH)
|
|
||||||
expect(await proposalCreator.UniswapV3Factory()).to.be.equal(config.UniswapV3Factory)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Governance should be able to set factory/proposalCreator params', async function () {
|
|
||||||
let { instanceFactory, proposalCreator, gov } = await loadFixture(fixture)
|
|
||||||
|
|
||||||
await expect(instanceFactory.setMerkleTreeHeight(1)).to.be.reverted
|
|
||||||
|
|
||||||
const govSigner = await getSignerFromAddress(gov.address)
|
|
||||||
instanceFactory = await instanceFactory.connect(govSigner)
|
|
||||||
proposalCreator = await proposalCreator.connect(govSigner)
|
|
||||||
|
|
||||||
await instanceFactory.generateNewImplementation(addressZero, addressZero)
|
|
||||||
await instanceFactory.setMerkleTreeHeight(1)
|
|
||||||
await proposalCreator.setCreationFee(0)
|
|
||||||
await proposalCreator.setTWAPSlotsMin(0)
|
|
||||||
|
|
||||||
expect(await instanceFactory.verifier()).to.be.equal(addressZero)
|
|
||||||
expect(await instanceFactory.hasher()).to.be.equal(addressZero)
|
|
||||||
expect(await instanceFactory.merkleTreeHeight()).to.be.equal(1)
|
|
||||||
expect(await proposalCreator.creationFee()).to.be.equal(0)
|
|
||||||
expect(await proposalCreator.TWAPSlotsMin()).to.be.equal(0)
|
|
||||||
|
|
||||||
await instanceFactory.generateNewImplementation(config.verifier, config.hasher)
|
|
||||||
await instanceFactory.setMerkleTreeHeight(config.merkleTreeHeight)
|
|
||||||
await proposalCreator.setCreationFee(config.creationFee)
|
|
||||||
await proposalCreator.setTWAPSlotsMin(config.TWAPSlotsMin)
|
|
||||||
|
|
||||||
expect(await instanceFactory.verifier()).to.be.equal(config.verifier)
|
|
||||||
expect(await instanceFactory.hasher()).to.be.equal(config.hasher)
|
|
||||||
expect(await instanceFactory.merkleTreeHeight()).to.be.equal(config.merkleTreeHeight)
|
|
||||||
expect(await proposalCreator.creationFee()).to.be.equal(config.creationFee)
|
|
||||||
expect(await proposalCreator.TWAPSlotsMin()).to.be.equal(config.TWAPSlotsMin)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should successfully deploy/propose/execute proposal - add instance', async function () {
|
|
||||||
let { sender, instanceFactory, proposalCreator, gov, instanceRegistry, tornWhale, tornToken } =
|
|
||||||
await loadFixture(fixture)
|
|
||||||
|
|
||||||
// deploy proposal ----------------------------------------------
|
|
||||||
await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee)
|
|
||||||
await tornToken.approve(proposalCreator.address, config.creationFee)
|
|
||||||
|
|
||||||
await expect(() =>
|
|
||||||
proposalCreator
|
|
||||||
.connect(sender)
|
|
||||||
.createProposalApprove(config.COMP, 3000, [ethers.utils.parseEther('100')], [30]),
|
|
||||||
).to.changeTokenBalances(
|
|
||||||
tornToken,
|
|
||||||
[sender, gov],
|
|
||||||
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
|
|
||||||
)
|
|
||||||
|
|
||||||
let logs = await proposalCreator.queryFilter('NewGovernanceProposalCreated')
|
|
||||||
const proposal = await ethers.getContractAt(
|
|
||||||
'AddInstanceProposal',
|
|
||||||
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(await proposal.instanceFactory()).to.be.equal(instanceFactory.address)
|
|
||||||
expect(await proposal.instanceRegistry()).to.be.equal(instanceRegistry.address)
|
|
||||||
expect(await proposal.token()).to.be.equal(config.COMP)
|
|
||||||
expect(await proposal.uniswapPoolSwappingFee()).to.be.equal(3000)
|
|
||||||
expect(await proposal.numInstances()).to.be.equal(1)
|
|
||||||
expect(await proposal.protocolFeeByIndex(0)).to.be.equal(30)
|
|
||||||
expect(await proposal.denominationByIndex(0)).to.be.equal(ethers.utils.parseEther('100'))
|
|
||||||
|
|
||||||
// propose proposal ---------------------------------------------
|
|
||||||
let response, id, state
|
|
||||||
gov = await gov.connect(tornWhale)
|
|
||||||
await tornToken.connect(tornWhale).approve(gov.address, ethers.utils.parseEther('26000'))
|
|
||||||
await gov.lockWithApproval(ethers.utils.parseEther('26000'))
|
|
||||||
|
|
||||||
response = await gov.propose(proposal.address, 'COMP token instance proposal')
|
|
||||||
id = await gov.latestProposalIds(tornWhale.address)
|
|
||||||
state = await gov.state(id)
|
|
||||||
|
|
||||||
const { events } = await response.wait()
|
|
||||||
const args = events.find(({ event }) => event == 'ProposalCreated').args
|
|
||||||
expect(args.id).to.be.equal(id)
|
|
||||||
expect(args.proposer).to.be.equal(tornWhale.address)
|
|
||||||
expect(args.target).to.be.equal(proposal.address)
|
|
||||||
expect(args.description).to.be.equal('COMP token instance proposal')
|
|
||||||
expect(state).to.be.equal(ProposalState.Pending)
|
|
||||||
|
|
||||||
// execute proposal ---------------------------------------------
|
|
||||||
await minewait((await gov.VOTING_DELAY()).add(1).toNumber())
|
|
||||||
await expect(gov.castVote(id, true)).to.not.be.reverted
|
|
||||||
expect(await gov.state(id)).to.be.equal(ProposalState.Active)
|
|
||||||
await minewait(
|
|
||||||
(
|
|
||||||
await gov.VOTING_PERIOD()
|
|
||||||
)
|
|
||||||
.add(await gov.EXECUTION_DELAY())
|
|
||||||
.add(96400)
|
|
||||||
.toNumber(),
|
|
||||||
)
|
|
||||||
expect(await gov.state(id)).to.be.equal(ProposalState.AwaitingExecution)
|
|
||||||
|
|
||||||
let tx = await gov.execute(id)
|
|
||||||
|
|
||||||
expect(await gov.state(id)).to.be.equal(ProposalState.Executed)
|
|
||||||
|
|
||||||
// check instance initialization --------------------------------
|
|
||||||
let receipt = await tx.wait()
|
|
||||||
const instanceAddr = '0x' + receipt.events[0].topics[1].toString().slice(-40)
|
|
||||||
const instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddr)
|
|
||||||
|
|
||||||
expect(await instance.token()).to.be.equal(config.COMP)
|
|
||||||
expect(await instance.verifier()).to.be.equal(config.verifier)
|
|
||||||
expect(await instance.hasher()).to.be.equal(config.hasher)
|
|
||||||
expect(await instance.levels()).to.be.equal(config.merkleTreeHeight)
|
|
||||||
expect(await instance.denomination()).to.equal(ethers.utils.parseEther('100'))
|
|
||||||
|
|
||||||
const instanceData = await instanceRegistry.instances(instance.address)
|
|
||||||
expect(instanceData.isERC20).to.be.equal(true)
|
|
||||||
expect(instanceData.token).to.be.equal(config.COMP)
|
|
||||||
expect(instanceData.state).to.be.equal(1)
|
|
||||||
expect(instanceData.uniswapPoolSwappingFee).to.be.equal(3000)
|
|
||||||
expect(instanceData.protocolFeePercentage).to.be.equal(30)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should successfully deploy/propose/execute proposal - add instances', async function () {
|
|
||||||
let { sender, instanceFactory, proposalCreator, gov, instanceRegistry, tornWhale, tornToken } =
|
|
||||||
await loadFixture(fixture)
|
|
||||||
|
|
||||||
const denominations = [
|
|
||||||
ethers.utils.parseEther('1'),
|
|
||||||
ethers.utils.parseEther('10'),
|
|
||||||
ethers.utils.parseEther('100'),
|
|
||||||
ethers.utils.parseEther('1000'),
|
|
||||||
]
|
|
||||||
const numInstances = denominations.length
|
|
||||||
|
|
||||||
const protocolFees = [30, 30, 30, 30]
|
|
||||||
|
|
||||||
// deploy proposal ----------------------------------------------
|
|
||||||
await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee)
|
|
||||||
await tornToken.approve(proposalCreator.address, config.creationFee)
|
|
||||||
|
|
||||||
await expect(() =>
|
|
||||||
proposalCreator.connect(sender).createProposalApprove(config.COMP, 3000, denominations, protocolFees),
|
|
||||||
).to.changeTokenBalances(
|
|
||||||
tornToken,
|
|
||||||
[sender, gov],
|
|
||||||
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
|
|
||||||
)
|
|
||||||
|
|
||||||
let logs = await proposalCreator.queryFilter('NewGovernanceProposalCreated')
|
|
||||||
const proposal = await ethers.getContractAt(
|
|
||||||
'AddInstanceProposal',
|
|
||||||
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(await proposal.instanceFactory()).to.be.equal(instanceFactory.address)
|
|
||||||
expect(await proposal.instanceRegistry()).to.be.equal(instanceRegistry.address)
|
|
||||||
expect(await proposal.token()).to.be.equal(config.COMP)
|
|
||||||
expect(await proposal.uniswapPoolSwappingFee()).to.be.equal(3000)
|
|
||||||
expect(await proposal.numInstances()).to.be.equal(numInstances)
|
|
||||||
for (let i = 0; i < numInstances; i++) {
|
|
||||||
expect(await proposal.protocolFeeByIndex(i)).to.be.equal(protocolFees[i])
|
|
||||||
expect(await proposal.denominationByIndex(i)).to.be.equal(denominations[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
// propose proposal ---------------------------------------------
|
|
||||||
let response, id, state
|
|
||||||
gov = await gov.connect(tornWhale)
|
|
||||||
await tornToken.connect(tornWhale).approve(gov.address, ethers.utils.parseEther('26000'))
|
|
||||||
await gov.lockWithApproval(ethers.utils.parseEther('26000'))
|
|
||||||
|
|
||||||
response = await gov.propose(proposal.address, 'COMP token instances proposal')
|
|
||||||
id = await gov.latestProposalIds(tornWhale.address)
|
|
||||||
state = await gov.state(id)
|
|
||||||
|
|
||||||
const { events } = await response.wait()
|
|
||||||
const args = events.find(({ event }) => event == 'ProposalCreated').args
|
|
||||||
expect(args.id).to.be.equal(id)
|
|
||||||
expect(args.proposer).to.be.equal(tornWhale.address)
|
|
||||||
expect(args.target).to.be.equal(proposal.address)
|
|
||||||
expect(args.description).to.be.equal('COMP token instances proposal')
|
|
||||||
expect(state).to.be.equal(ProposalState.Pending)
|
|
||||||
|
|
||||||
// execute proposal ---------------------------------------------
|
|
||||||
await minewait((await gov.VOTING_DELAY()).add(1).toNumber())
|
|
||||||
await expect(gov.castVote(id, true)).to.not.be.reverted
|
|
||||||
expect(await gov.state(id)).to.be.equal(ProposalState.Active)
|
|
||||||
await minewait(
|
|
||||||
(
|
|
||||||
await gov.VOTING_PERIOD()
|
|
||||||
)
|
|
||||||
.add(await gov.EXECUTION_DELAY())
|
|
||||||
.add(96400)
|
|
||||||
.toNumber(),
|
|
||||||
)
|
|
||||||
expect(await gov.state(id)).to.be.equal(ProposalState.AwaitingExecution)
|
|
||||||
|
|
||||||
await gov.execute(id)
|
|
||||||
|
|
||||||
expect(await gov.state(id)).to.be.equal(ProposalState.Executed)
|
|
||||||
|
|
||||||
// check instances initialization -------------------------------
|
|
||||||
logs = await instanceFactory.queryFilter('NewInstanceCloneCreated')
|
|
||||||
for (let i = 0; i < numInstances; i++) {
|
|
||||||
let instanceAddr = '0x' + logs[logs.length - numInstances + i].topics[1].slice(-40)
|
|
||||||
let instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddr)
|
|
||||||
|
|
||||||
expect(await instance.token()).to.be.equal(config.COMP)
|
|
||||||
expect(await instance.verifier()).to.be.equal(config.verifier)
|
|
||||||
expect(await instance.hasher()).to.be.equal(config.hasher)
|
|
||||||
expect(await instance.levels()).to.be.equal(config.merkleTreeHeight)
|
|
||||||
expect(await instance.denomination()).to.equal(denominations[i])
|
|
||||||
|
|
||||||
let instanceData = await instanceRegistry.instances(instance.address)
|
|
||||||
expect(instanceData.isERC20).to.be.equal(true)
|
|
||||||
expect(instanceData.token).to.be.equal(config.COMP)
|
|
||||||
expect(instanceData.state).to.be.equal(1)
|
|
||||||
expect(instanceData.uniswapPoolSwappingFee).to.be.equal(3000)
|
|
||||||
expect(instanceData.protocolFeePercentage).to.be.equal(protocolFees[i])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should successfully deploy proposal with permit', async function () {
|
|
||||||
let { instanceFactory, proposalCreator, gov, instanceRegistry, tornWhale, tornToken } = await loadFixture(
|
|
||||||
fixture,
|
|
||||||
)
|
|
||||||
|
|
||||||
const privateKey = '0xc87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3'
|
|
||||||
const publicKey = '0x' + ethers.utils.computeAddress(Buffer.from(privateKey.slice(2), 'hex'))
|
|
||||||
const sender = await ethers.getSigner(publicKey.slice(2))
|
|
||||||
|
|
||||||
await expect(() =>
|
|
||||||
tornToken.connect(tornWhale).transfer(sender.address, config.creationFee),
|
|
||||||
).to.changeTokenBalances(
|
|
||||||
tornToken,
|
|
||||||
[tornWhale, sender],
|
|
||||||
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
|
|
||||||
)
|
|
||||||
|
|
||||||
// prepare permit data
|
|
||||||
const domain = {
|
|
||||||
name: await tornToken.name(),
|
|
||||||
version: '1',
|
|
||||||
chainId: 1,
|
|
||||||
verifyingContract: tornToken.address,
|
|
||||||
}
|
|
||||||
|
|
||||||
const curTimestamp = Math.trunc(new Date().getTime() / 1000)
|
|
||||||
const args = {
|
|
||||||
owner: sender,
|
|
||||||
spender: proposalCreator.address,
|
|
||||||
value: config.creationFee,
|
|
||||||
nonce: 0,
|
|
||||||
deadline: curTimestamp + 1000,
|
|
||||||
}
|
|
||||||
|
|
||||||
const permitSigner = new PermitSigner(domain, args)
|
|
||||||
const signature = await permitSigner.getSignature(privateKey)
|
|
||||||
const signer = await permitSigner.getSignerAddress(args, signature.hex)
|
|
||||||
expect(signer).to.equal(sender.address)
|
|
||||||
|
|
||||||
await expect(() =>
|
|
||||||
proposalCreator.createProposalPermit(
|
|
||||||
config.COMP,
|
|
||||||
3000,
|
|
||||||
[ethers.utils.parseEther('100')],
|
|
||||||
[30],
|
|
||||||
sender.address,
|
|
||||||
args.deadline.toString(),
|
|
||||||
signature.v,
|
|
||||||
signature.r,
|
|
||||||
signature.s,
|
|
||||||
),
|
|
||||||
).to.changeTokenBalances(
|
|
||||||
tornToken,
|
|
||||||
[sender, gov],
|
|
||||||
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
|
|
||||||
)
|
|
||||||
|
|
||||||
let logs = await proposalCreator.queryFilter('NewGovernanceProposalCreated')
|
|
||||||
const proposal = await ethers.getContractAt(
|
|
||||||
'AddInstanceProposal',
|
|
||||||
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(await proposal.instanceFactory()).to.be.equal(instanceFactory.address)
|
|
||||||
expect(await proposal.instanceRegistry()).to.be.equal(instanceRegistry.address)
|
|
||||||
expect(await proposal.token()).to.be.equal(config.COMP)
|
|
||||||
expect(await proposal.uniswapPoolSwappingFee()).to.be.equal(3000)
|
|
||||||
expect(await proposal.numInstances()).to.be.equal(1)
|
|
||||||
expect(await proposal.protocolFeeByIndex(0)).to.be.equal(30)
|
|
||||||
expect(await proposal.denominationByIndex(0)).to.be.equal(ethers.utils.parseEther('100'))
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should deposit and withdraw into the new instance', async function () {
|
|
||||||
let { sender, proposalCreator, gov, tornWhale, tornToken, router, compToken, compWhale } =
|
|
||||||
await loadFixture(fixture)
|
|
||||||
|
|
||||||
// deploy proposal ----------------------------------------------
|
|
||||||
await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee)
|
|
||||||
await tornToken.approve(proposalCreator.address, config.creationFee)
|
|
||||||
|
|
||||||
await expect(() =>
|
|
||||||
proposalCreator
|
|
||||||
.connect(sender)
|
|
||||||
.createProposalApprove(config.COMP, 3000, [ethers.utils.parseEther('100')], [30]),
|
|
||||||
).to.changeTokenBalances(
|
|
||||||
tornToken,
|
|
||||||
[sender, gov],
|
|
||||||
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
|
|
||||||
)
|
|
||||||
|
|
||||||
let logs = await proposalCreator.queryFilter('NewGovernanceProposalCreated')
|
|
||||||
const proposal = await ethers.getContractAt(
|
|
||||||
'AddInstanceProposal',
|
|
||||||
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
|
|
||||||
)
|
|
||||||
|
|
||||||
// propose proposal ---------------------------------------------
|
|
||||||
let id
|
|
||||||
gov = await gov.connect(tornWhale)
|
|
||||||
await tornToken.connect(tornWhale).approve(gov.address, ethers.utils.parseEther('26000'))
|
|
||||||
await gov.lockWithApproval(ethers.utils.parseEther('26000'))
|
|
||||||
|
|
||||||
await gov.propose(proposal.address, 'COMP token instance proposal')
|
|
||||||
id = await gov.latestProposalIds(tornWhale.address)
|
|
||||||
|
|
||||||
// execute proposal ---------------------------------------------
|
|
||||||
await minewait((await gov.VOTING_DELAY()).add(1).toNumber())
|
|
||||||
await expect(gov.castVote(id, true)).to.not.be.reverted
|
|
||||||
await minewait(
|
|
||||||
(
|
|
||||||
await gov.VOTING_PERIOD()
|
|
||||||
)
|
|
||||||
.add(await gov.EXECUTION_DELAY())
|
|
||||||
.add(96400)
|
|
||||||
.toNumber(),
|
|
||||||
)
|
|
||||||
|
|
||||||
let tx = await gov.execute(id)
|
|
||||||
let receipt = await tx.wait()
|
|
||||||
const instanceAddr = '0x' + receipt.events[0].topics[1].toString().slice(-40)
|
|
||||||
const instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddr)
|
|
||||||
|
|
||||||
// check instance work ------------------------------------------
|
|
||||||
const depo = createDeposit({
|
|
||||||
nullifier: rbigint(31),
|
|
||||||
secret: rbigint(31),
|
|
||||||
})
|
|
||||||
|
|
||||||
const value = ethers.utils.parseEther('100')
|
|
||||||
|
|
||||||
await compToken.connect(compWhale).transfer(sender.address, value)
|
|
||||||
await compToken.connect(sender).approve(router.address, value)
|
|
||||||
|
|
||||||
await expect(() => router.deposit(instance.address, toHex(depo.commitment), [])).to.changeTokenBalances(
|
|
||||||
compToken,
|
|
||||||
[sender, instance],
|
|
||||||
[BigNumber.from(0).sub(value), value],
|
|
||||||
)
|
|
||||||
|
|
||||||
let pevents = await instance.queryFilter('Deposit')
|
|
||||||
await initialize({ merkleTreeHeight: 20 })
|
|
||||||
|
|
||||||
const { proof, args } = await generateProof({
|
|
||||||
deposit: depo,
|
|
||||||
recipient: sender.address,
|
|
||||||
events: pevents,
|
|
||||||
})
|
|
||||||
|
|
||||||
await expect(() => router.withdraw(instance.address, proof, ...args)).to.changeTokenBalances(
|
|
||||||
compToken,
|
|
||||||
[instance, sender],
|
|
||||||
[BigNumber.from(0).sub(value), value],
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should not deploy proposal with incorrect Uniswap pool', async function () {
|
|
||||||
let { sender, proposalCreator, tornWhale, tornToken } = await loadFixture(fixture)
|
|
||||||
|
|
||||||
// deploy proposal ----------------------------------------------
|
|
||||||
await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee)
|
|
||||||
await tornToken.approve(proposalCreator.address, config.creationFee)
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
proposalCreator
|
|
||||||
.connect(sender)
|
|
||||||
.createProposalApprove(config.COMP, 4000, [ethers.utils.parseEther('100')], [30]),
|
|
||||||
).to.be.revertedWith('Uniswap pool is not exist')
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
proposalCreator
|
|
||||||
.connect(sender)
|
|
||||||
.createProposalApprove(config.COMP, 10000, [ethers.utils.parseEther('100')], [30]),
|
|
||||||
).to.be.revertedWith('Uniswap pool TWAP slots number is low')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should not deploy proposal with incorrect protocol fee', async function () {
|
|
||||||
let { sender, proposalCreator, tornWhale, tornToken } = await loadFixture(fixture)
|
|
||||||
|
|
||||||
// deploy proposal ----------------------------------------------
|
|
||||||
await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee)
|
|
||||||
await tornToken.approve(proposalCreator.address, config.creationFee)
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
proposalCreator
|
|
||||||
.connect(sender)
|
|
||||||
.createProposalApprove(config.COMP, 3000, [ethers.utils.parseEther('100')], [10300]),
|
|
||||||
).to.be.revertedWith('Protocol fee is more than 100%')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should successfully deploy/propose/execute proposal - add native instance', async function () {
|
|
||||||
let { sender, instanceFactory, proposalCreator, gov, instanceRegistry, tornWhale, tornToken } =
|
|
||||||
await loadFixture(fixture)
|
|
||||||
|
|
||||||
const denomination = ethers.utils.parseEther('1.5')
|
|
||||||
|
|
||||||
// deploy proposal ----------------------------------------------
|
|
||||||
await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee)
|
|
||||||
await tornToken.approve(proposalCreator.address, config.creationFee)
|
|
||||||
|
|
||||||
await expect(() =>
|
|
||||||
proposalCreator.connect(sender).createProposalApprove(addressZero, 0, [denomination], [30]),
|
|
||||||
).to.changeTokenBalances(
|
|
||||||
tornToken,
|
|
||||||
[sender, gov],
|
|
||||||
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
|
|
||||||
)
|
|
||||||
|
|
||||||
let logs = await proposalCreator.queryFilter('NewGovernanceProposalCreated')
|
|
||||||
const proposal = await ethers.getContractAt(
|
|
||||||
'AddInstanceProposal',
|
|
||||||
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(await proposal.instanceFactory()).to.be.equal(instanceFactory.address)
|
|
||||||
expect(await proposal.instanceRegistry()).to.be.equal(instanceRegistry.address)
|
|
||||||
expect(await proposal.token()).to.be.equal(addressZero)
|
|
||||||
expect(await proposal.uniswapPoolSwappingFee()).to.be.equal(0)
|
|
||||||
expect(await proposal.numInstances()).to.be.equal(1)
|
|
||||||
expect(await proposal.protocolFeeByIndex(0)).to.be.equal(30)
|
|
||||||
expect(await proposal.denominationByIndex(0)).to.be.equal(denomination)
|
|
||||||
|
|
||||||
// propose proposal ---------------------------------------------
|
|
||||||
let response, id, state
|
|
||||||
gov = await gov.connect(tornWhale)
|
|
||||||
await tornToken.connect(tornWhale).approve(gov.address, ethers.utils.parseEther('26000'))
|
|
||||||
await gov.lockWithApproval(ethers.utils.parseEther('26000'))
|
|
||||||
|
|
||||||
response = await gov.propose(proposal.address, 'ETH 1.5 instance proposal')
|
|
||||||
id = await gov.latestProposalIds(tornWhale.address)
|
|
||||||
state = await gov.state(id)
|
|
||||||
|
|
||||||
const { events } = await response.wait()
|
|
||||||
const args = events.find(({ event }) => event == 'ProposalCreated').args
|
|
||||||
expect(args.id).to.be.equal(id)
|
|
||||||
expect(args.proposer).to.be.equal(tornWhale.address)
|
|
||||||
expect(args.target).to.be.equal(proposal.address)
|
|
||||||
expect(args.description).to.be.equal('ETH 1.5 instance proposal')
|
|
||||||
expect(state).to.be.equal(ProposalState.Pending)
|
|
||||||
|
|
||||||
// execute proposal ---------------------------------------------
|
|
||||||
await minewait((await gov.VOTING_DELAY()).add(1).toNumber())
|
|
||||||
await expect(gov.castVote(id, true)).to.not.be.reverted
|
|
||||||
expect(await gov.state(id)).to.be.equal(ProposalState.Active)
|
|
||||||
await minewait(
|
|
||||||
(
|
|
||||||
await gov.VOTING_PERIOD()
|
|
||||||
)
|
|
||||||
.add(await gov.EXECUTION_DELAY())
|
|
||||||
.add(96400)
|
|
||||||
.toNumber(),
|
|
||||||
)
|
|
||||||
expect(await gov.state(id)).to.be.equal(ProposalState.AwaitingExecution)
|
|
||||||
|
|
||||||
let tx = await gov.execute(id)
|
|
||||||
|
|
||||||
expect(await gov.state(id)).to.be.equal(ProposalState.Executed)
|
|
||||||
|
|
||||||
// check instance initialization --------------------------------
|
|
||||||
let receipt = await tx.wait()
|
|
||||||
const instanceAddr = '0x' + receipt.events[0].topics[1].toString().slice(-40)
|
|
||||||
const instance = await ethers.getContractAt('ETHTornadoCloneable', instanceAddr)
|
|
||||||
|
|
||||||
expect(await instance.verifier()).to.be.equal(config.verifier)
|
|
||||||
expect(await instance.hasher()).to.be.equal(config.hasher)
|
|
||||||
expect(await instance.levels()).to.be.equal(config.merkleTreeHeight)
|
|
||||||
expect(await instance.denomination()).to.equal(denomination)
|
|
||||||
|
|
||||||
const instanceData = await instanceRegistry.instances(instance.address)
|
|
||||||
expect(instanceData.isERC20).to.be.equal(false)
|
|
||||||
expect(instanceData.token).to.be.equal(addressZero)
|
|
||||||
expect(instanceData.state).to.be.equal(1)
|
|
||||||
expect(instanceData.uniswapPoolSwappingFee).to.be.equal(0)
|
|
||||||
expect(instanceData.protocolFeePercentage).to.be.equal(30)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should successfully deploy/propose/execute proposal - add native instances', async function () {
|
|
||||||
let { sender, instanceFactory, proposalCreator, gov, instanceRegistry, tornWhale, tornToken } =
|
|
||||||
await loadFixture(fixture)
|
|
||||||
|
|
||||||
const denominations = [
|
|
||||||
ethers.utils.parseEther('1.5'),
|
|
||||||
ethers.utils.parseEther('10.5'),
|
|
||||||
ethers.utils.parseEther('100.5'),
|
|
||||||
ethers.utils.parseEther('1000.5'),
|
|
||||||
]
|
|
||||||
const numInstances = denominations.length
|
|
||||||
|
|
||||||
const protocolFees = [30, 30, 30, 30]
|
|
||||||
|
|
||||||
// deploy proposal ----------------------------------------------
|
|
||||||
await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee)
|
|
||||||
await tornToken.approve(proposalCreator.address, config.creationFee)
|
|
||||||
|
|
||||||
await expect(() =>
|
|
||||||
proposalCreator.connect(sender).createProposalApprove(addressZero, 0, denominations, protocolFees),
|
|
||||||
).to.changeTokenBalances(
|
|
||||||
tornToken,
|
|
||||||
[sender, gov],
|
|
||||||
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
|
|
||||||
)
|
|
||||||
|
|
||||||
let logs = await proposalCreator.queryFilter('NewGovernanceProposalCreated')
|
|
||||||
const proposal = await ethers.getContractAt(
|
|
||||||
'AddInstanceProposal',
|
|
||||||
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(await proposal.instanceFactory()).to.be.equal(instanceFactory.address)
|
|
||||||
expect(await proposal.instanceRegistry()).to.be.equal(instanceRegistry.address)
|
|
||||||
expect(await proposal.token()).to.be.equal(addressZero)
|
|
||||||
expect(await proposal.uniswapPoolSwappingFee()).to.be.equal(0)
|
|
||||||
expect(await proposal.numInstances()).to.be.equal(numInstances)
|
|
||||||
for (let i = 0; i < numInstances; i++) {
|
|
||||||
expect(await proposal.protocolFeeByIndex(i)).to.be.equal(protocolFees[i])
|
|
||||||
expect(await proposal.denominationByIndex(i)).to.be.equal(denominations[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
// propose proposal ---------------------------------------------
|
|
||||||
let response, id, state
|
|
||||||
gov = await gov.connect(tornWhale)
|
|
||||||
await tornToken.connect(tornWhale).approve(gov.address, ethers.utils.parseEther('26000'))
|
|
||||||
await gov.lockWithApproval(ethers.utils.parseEther('26000'))
|
|
||||||
|
|
||||||
response = await gov.propose(proposal.address, 'ETH instances proposal')
|
|
||||||
id = await gov.latestProposalIds(tornWhale.address)
|
|
||||||
state = await gov.state(id)
|
|
||||||
|
|
||||||
const { events } = await response.wait()
|
|
||||||
const args = events.find(({ event }) => event == 'ProposalCreated').args
|
|
||||||
expect(args.id).to.be.equal(id)
|
|
||||||
expect(args.proposer).to.be.equal(tornWhale.address)
|
|
||||||
expect(args.target).to.be.equal(proposal.address)
|
|
||||||
expect(args.description).to.be.equal('ETH instances proposal')
|
|
||||||
expect(state).to.be.equal(ProposalState.Pending)
|
|
||||||
|
|
||||||
// execute proposal ---------------------------------------------
|
|
||||||
await minewait((await gov.VOTING_DELAY()).add(1).toNumber())
|
|
||||||
await expect(gov.castVote(id, true)).to.not.be.reverted
|
|
||||||
expect(await gov.state(id)).to.be.equal(ProposalState.Active)
|
|
||||||
await minewait(
|
|
||||||
(
|
|
||||||
await gov.VOTING_PERIOD()
|
|
||||||
)
|
|
||||||
.add(await gov.EXECUTION_DELAY())
|
|
||||||
.add(96400)
|
|
||||||
.toNumber(),
|
|
||||||
)
|
|
||||||
expect(await gov.state(id)).to.be.equal(ProposalState.AwaitingExecution)
|
|
||||||
|
|
||||||
await gov.execute(id)
|
|
||||||
|
|
||||||
expect(await gov.state(id)).to.be.equal(ProposalState.Executed)
|
|
||||||
|
|
||||||
// check instances initialization -------------------------------
|
|
||||||
logs = await instanceFactory.queryFilter('NewInstanceCloneCreated')
|
|
||||||
for (let i = 0; i < numInstances; i++) {
|
|
||||||
let instanceAddr = '0x' + logs[logs.length - numInstances + i].topics[1].slice(-40)
|
|
||||||
let instance = await ethers.getContractAt('ETHTornadoCloneable', instanceAddr)
|
|
||||||
|
|
||||||
expect(await instance.verifier()).to.be.equal(config.verifier)
|
|
||||||
expect(await instance.hasher()).to.be.equal(config.hasher)
|
|
||||||
expect(await instance.levels()).to.be.equal(config.merkleTreeHeight)
|
|
||||||
expect(await instance.denomination()).to.equal(denominations[i])
|
|
||||||
|
|
||||||
let instanceData = await instanceRegistry.instances(instance.address)
|
|
||||||
expect(instanceData.isERC20).to.be.equal(false)
|
|
||||||
expect(instanceData.token).to.be.equal(addressZero)
|
|
||||||
expect(instanceData.state).to.be.equal(1)
|
|
||||||
expect(instanceData.uniswapPoolSwappingFee).to.be.equal(0)
|
|
||||||
expect(instanceData.protocolFeePercentage).to.be.equal(protocolFees[i])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should successfully deploy proposal with permit for native', async function () {
|
|
||||||
let { instanceFactory, proposalCreator, gov, instanceRegistry, tornWhale, tornToken } = await loadFixture(
|
|
||||||
fixture,
|
|
||||||
)
|
|
||||||
|
|
||||||
const denomination = ethers.utils.parseEther('1.5')
|
|
||||||
|
|
||||||
const privateKey = '0xc87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3'
|
|
||||||
const publicKey = '0x' + ethers.utils.computeAddress(Buffer.from(privateKey.slice(2), 'hex'))
|
|
||||||
const sender = await ethers.getSigner(publicKey.slice(2))
|
|
||||||
|
|
||||||
await expect(() =>
|
|
||||||
tornToken.connect(tornWhale).transfer(sender.address, config.creationFee),
|
|
||||||
).to.changeTokenBalances(
|
|
||||||
tornToken,
|
|
||||||
[tornWhale, sender],
|
|
||||||
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
|
|
||||||
)
|
|
||||||
|
|
||||||
// prepare permit data
|
|
||||||
const domain = {
|
|
||||||
name: await tornToken.name(),
|
|
||||||
version: '1',
|
|
||||||
chainId: 1,
|
|
||||||
verifyingContract: tornToken.address,
|
|
||||||
}
|
|
||||||
|
|
||||||
const curTimestamp = Math.trunc(new Date().getTime() / 1000)
|
|
||||||
const args = {
|
|
||||||
owner: sender,
|
|
||||||
spender: proposalCreator.address,
|
|
||||||
value: config.creationFee,
|
|
||||||
nonce: 0,
|
|
||||||
deadline: curTimestamp + 1000,
|
|
||||||
}
|
|
||||||
|
|
||||||
const permitSigner = new PermitSigner(domain, args)
|
|
||||||
const signature = await permitSigner.getSignature(privateKey)
|
|
||||||
const signer = await permitSigner.getSignerAddress(args, signature.hex)
|
|
||||||
expect(signer).to.equal(sender.address)
|
|
||||||
|
|
||||||
await expect(() =>
|
|
||||||
proposalCreator.createProposalPermit(
|
|
||||||
addressZero,
|
|
||||||
0,
|
|
||||||
[denomination],
|
|
||||||
[30],
|
|
||||||
sender.address,
|
|
||||||
args.deadline.toString(),
|
|
||||||
signature.v,
|
|
||||||
signature.r,
|
|
||||||
signature.s,
|
|
||||||
),
|
|
||||||
).to.changeTokenBalances(
|
|
||||||
tornToken,
|
|
||||||
[sender, gov],
|
|
||||||
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
|
|
||||||
)
|
|
||||||
|
|
||||||
let logs = await proposalCreator.queryFilter('NewGovernanceProposalCreated')
|
|
||||||
const proposal = await ethers.getContractAt(
|
|
||||||
'AddInstanceProposal',
|
|
||||||
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(await proposal.instanceFactory()).to.be.equal(instanceFactory.address)
|
|
||||||
expect(await proposal.instanceRegistry()).to.be.equal(instanceRegistry.address)
|
|
||||||
expect(await proposal.token()).to.be.equal(addressZero)
|
|
||||||
expect(await proposal.uniswapPoolSwappingFee()).to.be.equal(0)
|
|
||||||
expect(await proposal.numInstances()).to.be.equal(1)
|
|
||||||
expect(await proposal.protocolFeeByIndex(0)).to.be.equal(30)
|
|
||||||
expect(await proposal.denominationByIndex(0)).to.be.equal(denomination)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should deposit and withdraw into the new native instance', async function () {
|
|
||||||
let { sender, proposalCreator, gov, tornWhale, tornToken, router } = await loadFixture(fixture)
|
|
||||||
|
|
||||||
const denomination = ethers.utils.parseEther('1.5')
|
|
||||||
|
|
||||||
// deploy proposal ----------------------------------------------
|
|
||||||
await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee)
|
|
||||||
await tornToken.approve(proposalCreator.address, config.creationFee)
|
|
||||||
|
|
||||||
await expect(() =>
|
|
||||||
proposalCreator.connect(sender).createProposalApprove(addressZero, 0, [denomination], [30]),
|
|
||||||
).to.changeTokenBalances(
|
|
||||||
tornToken,
|
|
||||||
[sender, gov],
|
|
||||||
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
|
|
||||||
)
|
|
||||||
|
|
||||||
let logs = await proposalCreator.queryFilter('NewGovernanceProposalCreated')
|
|
||||||
const proposal = await ethers.getContractAt(
|
|
||||||
'AddInstanceProposal',
|
|
||||||
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
|
|
||||||
)
|
|
||||||
|
|
||||||
// propose proposal ---------------------------------------------
|
|
||||||
let id
|
|
||||||
gov = await gov.connect(tornWhale)
|
|
||||||
await tornToken.connect(tornWhale).approve(gov.address, ethers.utils.parseEther('26000'))
|
|
||||||
await gov.lockWithApproval(ethers.utils.parseEther('26000'))
|
|
||||||
|
|
||||||
await gov.propose(proposal.address, 'ETH instance proposal')
|
|
||||||
id = await gov.latestProposalIds(tornWhale.address)
|
|
||||||
|
|
||||||
// execute proposal ---------------------------------------------
|
|
||||||
await minewait((await gov.VOTING_DELAY()).add(1).toNumber())
|
|
||||||
await expect(gov.castVote(id, true)).to.not.be.reverted
|
|
||||||
await minewait(
|
|
||||||
(
|
|
||||||
await gov.VOTING_PERIOD()
|
|
||||||
)
|
|
||||||
.add(await gov.EXECUTION_DELAY())
|
|
||||||
.add(96400)
|
|
||||||
.toNumber(),
|
|
||||||
)
|
|
||||||
|
|
||||||
let tx = await gov.execute(id)
|
|
||||||
let receipt = await tx.wait()
|
|
||||||
const instanceAddr = '0x' + receipt.events[0].topics[1].toString().slice(-40)
|
|
||||||
const instance = await ethers.getContractAt('ETHTornadoCloneable', instanceAddr)
|
|
||||||
|
|
||||||
// check instance work ------------------------------------------
|
|
||||||
const depo = createDeposit({
|
|
||||||
nullifier: rbigint(31),
|
|
||||||
secret: rbigint(31),
|
|
||||||
})
|
|
||||||
|
|
||||||
await expect(() =>
|
|
||||||
router.deposit(instance.address, toHex(depo.commitment), [], { value: denomination }),
|
|
||||||
).to.changeEtherBalances([sender, instance], [BigNumber.from(0).sub(denomination), denomination])
|
|
||||||
|
|
||||||
let pevents = await instance.queryFilter('Deposit')
|
|
||||||
await initialize({ merkleTreeHeight: 20 })
|
|
||||||
|
|
||||||
const { proof, args } = await generateProof({
|
|
||||||
deposit: depo,
|
|
||||||
recipient: sender.address,
|
|
||||||
events: pevents,
|
|
||||||
})
|
|
||||||
|
|
||||||
await expect(() => router.withdraw(instance.address, proof, ...args)).to.changeEtherBalances(
|
|
||||||
[instance, sender],
|
|
||||||
[BigNumber.from(0).sub(denomination), denomination],
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should not deploy native currency proposal with incorrect protocol fee', async function () {
|
|
||||||
let { sender, proposalCreator, tornWhale, tornToken } = await loadFixture(fixture)
|
|
||||||
|
|
||||||
// deploy proposal ----------------------------------------------
|
|
||||||
await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee)
|
|
||||||
await tornToken.approve(proposalCreator.address, config.creationFee)
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
proposalCreator
|
|
||||||
.connect(sender)
|
|
||||||
.createProposalApprove(addressZero, 0, [ethers.utils.parseEther('1.5')], [10300]),
|
|
||||||
).to.be.revertedWith('Protocol fee is more than 100%')
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,296 +0,0 @@
|
|||||||
const hre = require('hardhat')
|
|
||||||
const { ethers, waffle } = hre
|
|
||||||
const { loadFixture } = waffle
|
|
||||||
const { expect } = require('chai')
|
|
||||||
const { BigNumber } = require('@ethersproject/bignumber')
|
|
||||||
const config = require('../config')
|
|
||||||
const { getSignerFromAddress } = require('./utils')
|
|
||||||
const { generate } = require('../src/generateAddresses')
|
|
||||||
const { rbigint, createDeposit, toHex, generateProof, initialize } = require('tornado-cli')
|
|
||||||
|
|
||||||
describe('Sidechain Instance Factory Tests', () => {
|
|
||||||
const addressZero = ethers.constants.AddressZero
|
|
||||||
|
|
||||||
async function fixture() {
|
|
||||||
const [sender, deployer] = await ethers.getSigners()
|
|
||||||
|
|
||||||
const owner = await getSignerFromAddress(config.admin)
|
|
||||||
|
|
||||||
await sender.sendTransaction({
|
|
||||||
to: config.admin,
|
|
||||||
value: ethers.utils.parseEther('1'),
|
|
||||||
})
|
|
||||||
|
|
||||||
const compWhale = await getSignerFromAddress(config.compWhale)
|
|
||||||
|
|
||||||
const compToken = await ethers.getContractAt(
|
|
||||||
'@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20',
|
|
||||||
config.COMP,
|
|
||||||
)
|
|
||||||
|
|
||||||
// deploy InstanceFactory with CREATE2
|
|
||||||
const singletonFactory = await ethers.getContractAt(
|
|
||||||
'SingletonFactory',
|
|
||||||
config.singletonFactoryVerboseWrapper,
|
|
||||||
)
|
|
||||||
const contracts = await generate()
|
|
||||||
if ((await ethers.provider.getCode(contracts.sidechainFactory.implementation.address)) == '0x') {
|
|
||||||
await singletonFactory.deploy(contracts.sidechainFactory.implementation.bytecode, config.salt, {
|
|
||||||
gasLimit: config.deployGasLimit,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if ((await ethers.provider.getCode(contracts.sidechainFactory.proxy.address)) == '0x') {
|
|
||||||
await singletonFactory.deploy(contracts.sidechainFactory.proxy.bytecode, config.salt, {
|
|
||||||
gasLimit: config.deployGasLimit,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const instanceFactory = await ethers.getContractAt(
|
|
||||||
'SidechainInstanceFactory',
|
|
||||||
contracts.sidechainFactory.proxy.address,
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
sender,
|
|
||||||
deployer,
|
|
||||||
owner,
|
|
||||||
compToken,
|
|
||||||
compWhale,
|
|
||||||
instanceFactory,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
it('Should have initialized all successfully', async function () {
|
|
||||||
const { sender, compToken, instanceFactory } = await loadFixture(fixture)
|
|
||||||
expect(sender.address).to.exist
|
|
||||||
expect(compToken.address).to.exist
|
|
||||||
expect(instanceFactory.address).to.exist
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should set correct params for factory', async function () {
|
|
||||||
const { instanceFactory } = await loadFixture(fixture)
|
|
||||||
|
|
||||||
expect(await instanceFactory.verifier()).to.be.equal(config.verifier)
|
|
||||||
expect(await instanceFactory.hasher()).to.be.equal(config.hasher)
|
|
||||||
expect(await instanceFactory.merkleTreeHeight()).to.be.equal(config.merkleTreeHeight)
|
|
||||||
expect(await instanceFactory.ERC20Impl()).to.exist
|
|
||||||
expect(await instanceFactory.nativeCurImpl()).to.exist
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Admin should be able to set factory params', async function () {
|
|
||||||
let { instanceFactory, owner } = await loadFixture(fixture)
|
|
||||||
|
|
||||||
await expect(instanceFactory.setMerkleTreeHeight(1)).to.be.reverted
|
|
||||||
|
|
||||||
instanceFactory = await instanceFactory.connect(owner)
|
|
||||||
|
|
||||||
await instanceFactory.generateNewImplementation(addressZero, addressZero)
|
|
||||||
await instanceFactory.setMerkleTreeHeight(1)
|
|
||||||
|
|
||||||
expect(await instanceFactory.verifier()).to.be.equal(addressZero)
|
|
||||||
expect(await instanceFactory.hasher()).to.be.equal(addressZero)
|
|
||||||
expect(await instanceFactory.merkleTreeHeight()).to.be.equal(1)
|
|
||||||
|
|
||||||
await instanceFactory.generateNewImplementation(config.verifier, config.hasher)
|
|
||||||
await instanceFactory.setMerkleTreeHeight(config.merkleTreeHeight)
|
|
||||||
|
|
||||||
expect(await instanceFactory.verifier()).to.be.equal(config.verifier)
|
|
||||||
expect(await instanceFactory.hasher()).to.be.equal(config.hasher)
|
|
||||||
expect(await instanceFactory.merkleTreeHeight()).to.be.equal(config.merkleTreeHeight)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should successfully add instance', async function () {
|
|
||||||
let { sender, instanceFactory } = await loadFixture(fixture)
|
|
||||||
|
|
||||||
// deploy instance
|
|
||||||
await instanceFactory.connect(sender).createInstanceClone(ethers.utils.parseEther('1000'), config.COMP)
|
|
||||||
|
|
||||||
// check instance initialization
|
|
||||||
let logs = await instanceFactory.queryFilter('NewInstanceCloneCreated')
|
|
||||||
const instance = await ethers.getContractAt(
|
|
||||||
'ERC20TornadoCloneable',
|
|
||||||
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(await instance.token()).to.be.equal(config.COMP)
|
|
||||||
expect(await instance.verifier()).to.be.equal(config.verifier)
|
|
||||||
expect(await instance.hasher()).to.be.equal(config.hasher)
|
|
||||||
expect(await instance.levels()).to.be.equal(config.merkleTreeHeight)
|
|
||||||
expect(await instance.denomination()).to.equal(ethers.utils.parseEther('1000'))
|
|
||||||
|
|
||||||
// try to deploy the same instance again
|
|
||||||
await instanceFactory.connect(sender).createInstanceClone(ethers.utils.parseEther('1000'), config.COMP)
|
|
||||||
|
|
||||||
// check that instance has not been created - no new NewInstanceCloneCreated event
|
|
||||||
let curLogs = await instanceFactory.queryFilter('NewInstanceCloneCreated')
|
|
||||||
expect(curLogs.length).to.be.equal(logs.length)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should successfully add instances', async function () {
|
|
||||||
let { sender, instanceFactory } = await loadFixture(fixture)
|
|
||||||
|
|
||||||
const denominations = [
|
|
||||||
ethers.utils.parseEther('1'),
|
|
||||||
ethers.utils.parseEther('10'),
|
|
||||||
ethers.utils.parseEther('100'),
|
|
||||||
ethers.utils.parseEther('1000'),
|
|
||||||
]
|
|
||||||
const numInstances = denominations.length
|
|
||||||
|
|
||||||
// deploy instances
|
|
||||||
await instanceFactory.connect(sender).createInstanceClones(config.COMP, denominations)
|
|
||||||
|
|
||||||
// check instance initialization
|
|
||||||
let logs = await instanceFactory.queryFilter('NewInstanceCloneCreated')
|
|
||||||
for (let i = 0; i < numInstances; i++) {
|
|
||||||
let instanceAddr = '0x' + logs[logs.length - numInstances + i].topics[1].slice(-40)
|
|
||||||
let instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddr)
|
|
||||||
|
|
||||||
expect(await instance.token()).to.be.equal(config.COMP)
|
|
||||||
expect(await instance.verifier()).to.be.equal(config.verifier)
|
|
||||||
expect(await instance.hasher()).to.be.equal(config.hasher)
|
|
||||||
expect(await instance.levels()).to.be.equal(config.merkleTreeHeight)
|
|
||||||
expect(await instance.denomination()).to.equal(denominations[i])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should deposit and withdraw into the new instance', async function () {
|
|
||||||
let { sender, instanceFactory, compToken, compWhale } = await loadFixture(fixture)
|
|
||||||
|
|
||||||
// deploy instance
|
|
||||||
await instanceFactory.connect(sender).createInstanceClone(ethers.utils.parseEther('100'), config.COMP)
|
|
||||||
|
|
||||||
let logs = await instanceFactory.queryFilter('NewInstanceCloneCreated')
|
|
||||||
const instance = await ethers.getContractAt(
|
|
||||||
'ERC20TornadoCloneable',
|
|
||||||
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
|
|
||||||
)
|
|
||||||
|
|
||||||
// check instance work ------------------------------------------
|
|
||||||
const depo = createDeposit({
|
|
||||||
nullifier: rbigint(31),
|
|
||||||
secret: rbigint(31),
|
|
||||||
})
|
|
||||||
|
|
||||||
const value = ethers.utils.parseEther('100')
|
|
||||||
|
|
||||||
await compToken.connect(compWhale).transfer(sender.address, value)
|
|
||||||
await compToken.connect(sender).approve(instance.address, value)
|
|
||||||
|
|
||||||
await expect(() => instance.deposit(toHex(depo.commitment), [])).to.changeTokenBalances(
|
|
||||||
compToken,
|
|
||||||
[sender, instance],
|
|
||||||
[BigNumber.from(0).sub(value), value],
|
|
||||||
)
|
|
||||||
|
|
||||||
let pevents = await instance.queryFilter('Deposit')
|
|
||||||
await initialize({ merkleTreeHeight: 20 })
|
|
||||||
|
|
||||||
const { proof, args } = await generateProof({
|
|
||||||
deposit: depo,
|
|
||||||
recipient: sender.address,
|
|
||||||
events: pevents,
|
|
||||||
})
|
|
||||||
|
|
||||||
await expect(() => instance.withdraw(proof, ...args)).to.changeTokenBalances(
|
|
||||||
compToken,
|
|
||||||
[instance, sender],
|
|
||||||
[BigNumber.from(0).sub(value), value],
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should successfully add native currency instance', async function () {
|
|
||||||
let { sender, instanceFactory } = await loadFixture(fixture)
|
|
||||||
|
|
||||||
const denomination = ethers.utils.parseEther('1')
|
|
||||||
|
|
||||||
// deploy instance
|
|
||||||
await instanceFactory.connect(sender).createInstanceClone(denomination, addressZero)
|
|
||||||
|
|
||||||
// check instance initialization
|
|
||||||
let logs = await instanceFactory.queryFilter('NewInstanceCloneCreated')
|
|
||||||
const instance = await ethers.getContractAt(
|
|
||||||
'ETHTornadoCloneable',
|
|
||||||
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(await instance.verifier()).to.be.equal(config.verifier)
|
|
||||||
expect(await instance.hasher()).to.be.equal(config.hasher)
|
|
||||||
expect(await instance.levels()).to.be.equal(config.merkleTreeHeight)
|
|
||||||
expect(await instance.denomination()).to.equal(denomination)
|
|
||||||
|
|
||||||
// try to deploy the same instance again
|
|
||||||
await instanceFactory.connect(sender).createInstanceClone(denomination, addressZero)
|
|
||||||
|
|
||||||
// check that instance has not been created - no new NewInstanceCloneCreated event
|
|
||||||
let curLogs = await instanceFactory.queryFilter('NewInstanceCloneCreated')
|
|
||||||
expect(curLogs.length).to.be.equal(logs.length)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should successfully add native currency instances', async function () {
|
|
||||||
let { sender, instanceFactory } = await loadFixture(fixture)
|
|
||||||
|
|
||||||
const denominations = [
|
|
||||||
ethers.utils.parseEther('1'),
|
|
||||||
ethers.utils.parseEther('10'),
|
|
||||||
ethers.utils.parseEther('100'),
|
|
||||||
ethers.utils.parseEther('1000'),
|
|
||||||
]
|
|
||||||
const numInstances = denominations.length
|
|
||||||
|
|
||||||
// deploy instances
|
|
||||||
await instanceFactory.connect(sender).createInstanceClones(addressZero, denominations)
|
|
||||||
|
|
||||||
// check instance initialization
|
|
||||||
let logs = await instanceFactory.queryFilter('NewInstanceCloneCreated')
|
|
||||||
for (let i = 0; i < numInstances; i++) {
|
|
||||||
let instanceAddr = '0x' + logs[logs.length - numInstances + i].topics[1].slice(-40)
|
|
||||||
let instance = await ethers.getContractAt('ETHTornadoCloneable', instanceAddr)
|
|
||||||
|
|
||||||
expect(await instance.verifier()).to.be.equal(config.verifier)
|
|
||||||
expect(await instance.hasher()).to.be.equal(config.hasher)
|
|
||||||
expect(await instance.levels()).to.be.equal(config.merkleTreeHeight)
|
|
||||||
expect(await instance.denomination()).to.equal(denominations[i])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should deposit and withdraw into the new native currency instance', async function () {
|
|
||||||
let { sender, instanceFactory } = await loadFixture(fixture)
|
|
||||||
|
|
||||||
const denomination = ethers.utils.parseEther('1.5')
|
|
||||||
|
|
||||||
// deploy instance
|
|
||||||
await instanceFactory.connect(sender).createInstanceClone(denomination, addressZero)
|
|
||||||
|
|
||||||
let logs = await instanceFactory.queryFilter('NewInstanceCloneCreated')
|
|
||||||
const instance = await ethers.getContractAt(
|
|
||||||
'ETHTornadoCloneable',
|
|
||||||
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
|
|
||||||
)
|
|
||||||
|
|
||||||
// check instance work ------------------------------------------
|
|
||||||
const depo = createDeposit({
|
|
||||||
nullifier: rbigint(31),
|
|
||||||
secret: rbigint(31),
|
|
||||||
})
|
|
||||||
|
|
||||||
await expect(() =>
|
|
||||||
instance.connect(sender).deposit(toHex(depo.commitment), {
|
|
||||||
value: denomination,
|
|
||||||
}),
|
|
||||||
).to.changeEtherBalances([sender, instance], [BigNumber.from(0).sub(denomination), denomination])
|
|
||||||
|
|
||||||
let pevents = await instance.queryFilter('Deposit')
|
|
||||||
await initialize({ merkleTreeHeight: 20 })
|
|
||||||
|
|
||||||
const { proof, args } = await generateProof({
|
|
||||||
deposit: depo,
|
|
||||||
recipient: sender.address,
|
|
||||||
events: pevents,
|
|
||||||
})
|
|
||||||
|
|
||||||
await expect(() => instance.withdraw(proof, ...args)).to.changeEtherBalances(
|
|
||||||
[instance, sender],
|
|
||||||
[BigNumber.from(0).sub(denomination), denomination],
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,43 +0,0 @@
|
|||||||
/* global ethers, network */
|
|
||||||
|
|
||||||
async function setTime(timestamp) {
|
|
||||||
await ethers.provider.send('evm_setNextBlockTimestamp', [timestamp])
|
|
||||||
}
|
|
||||||
|
|
||||||
async function takeSnapshot() {
|
|
||||||
return await ethers.provider.send('evm_snapshot', [])
|
|
||||||
}
|
|
||||||
|
|
||||||
async function revertSnapshot(id) {
|
|
||||||
await ethers.provider.send('evm_revert', [id])
|
|
||||||
}
|
|
||||||
|
|
||||||
async function advanceTime(sec) {
|
|
||||||
const now = (await ethers.provider.getBlock('latest')).timestamp
|
|
||||||
await setTime(now + sec)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getSignerFromAddress(address) {
|
|
||||||
await network.provider.request({
|
|
||||||
method: 'hardhat_impersonateAccount',
|
|
||||||
params: [address],
|
|
||||||
})
|
|
||||||
|
|
||||||
let signer = await ethers.provider.getSigner(address)
|
|
||||||
signer.address = signer._address
|
|
||||||
return signer
|
|
||||||
}
|
|
||||||
|
|
||||||
async function minewait(time) {
|
|
||||||
await ethers.provider.send('evm_increaseTime', [time])
|
|
||||||
await ethers.provider.send('evm_mine', [])
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
setTime,
|
|
||||||
advanceTime,
|
|
||||||
takeSnapshot,
|
|
||||||
revertSnapshot,
|
|
||||||
getSignerFromAddress,
|
|
||||||
minewait,
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user