deploy script and code quality on infra proposal
Signed-off-by: AlienTornadosaurusHex <>
This commit is contained in:
parent
f1aabff16b
commit
c34a70585a
@ -2,3 +2,11 @@
|
||||
MAINNET_RPC_URL=
|
||||
ETHERSCAN_KEY=
|
||||
PRIVATE_KEY=
|
||||
|
||||
# Deployed contracts, which are needed for other deployments.
|
||||
DEPLOYED_ROUTER_ADDRESS=
|
||||
DEPLOYED_STAKING_ADDRESS=
|
||||
DEPLOYED_RELAYERSR_ADDRESS=
|
||||
DEPLOYED_INSTANCESR_ADDRESS=
|
||||
DEPLOYED_FOM_ADDRESS=
|
||||
DEPLOYED_UNIFEO_ADDRESS=
|
107
script/Deploys.sol
Normal file
107
script/Deploys.sol
Normal file
@ -0,0 +1,107 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.6.12;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
// STD imports
|
||||
|
||||
import { Script } from "forge-std/Script.sol";
|
||||
|
||||
import { console2 } from "forge-std/console2.sol";
|
||||
|
||||
// Local imports
|
||||
|
||||
import { TornadoAddresses } from "../src/common/TornadoAddresses.sol";
|
||||
|
||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ BASE CONTRACTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||
|
||||
abstract contract SimpleDeployer is TornadoAddresses, Script {
|
||||
function _createContract() internal virtual;
|
||||
|
||||
function run() external {
|
||||
uint256 key = vm.envUint("PRIVATE_KEY");
|
||||
|
||||
vm.startBroadcast(key);
|
||||
|
||||
_createContract();
|
||||
|
||||
vm.stopBroadcast();
|
||||
}
|
||||
}
|
||||
|
||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ DEPLOY ROUTER ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||
|
||||
import { TornadoRouter } from "../src/v2/TornadoRouter.sol";
|
||||
|
||||
contract RouterDeployer is SimpleDeployer {
|
||||
function _createContract() internal virtual override {
|
||||
new TornadoRouter(getGovernanceProxyAddress());
|
||||
}
|
||||
}
|
||||
|
||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ DEPLOY STAKING ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||
|
||||
import { TornadoStakingRewards } from "../src/v2/TornadoStakingRewards.sol";
|
||||
|
||||
contract StakingDeployer is SimpleDeployer {
|
||||
function _createContract() internal virtual override {
|
||||
new TornadoStakingRewards(getGovernanceProxyAddress(), getTornTokenAddress(), vm.envAddress("DEPLOYED_ROUTER_ADDRESS"));
|
||||
}
|
||||
}
|
||||
|
||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ DEPLOY RELAYER REGISTRY ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||
|
||||
import { RelayerRegistry } from "../src/v2/RelayerRegistry.sol";
|
||||
|
||||
contract RelayerRegistryDeployer is SimpleDeployer {
|
||||
function _createContract() internal virtual override {
|
||||
new RelayerRegistry(getGovernanceProxyAddress(), getTornTokenAddress());
|
||||
}
|
||||
}
|
||||
|
||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ DEPLOY INSTANCE REGISTRY ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||
|
||||
import { InstanceRegistry } from "../src/v2/InstanceRegistry.sol";
|
||||
|
||||
contract InstanceRegistryDeployer is SimpleDeployer {
|
||||
function _createContract() internal virtual override {
|
||||
new InstanceRegistry(getGovernanceProxyAddress());
|
||||
}
|
||||
}
|
||||
|
||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ DEPLOY FEE ORACLE MANAGER ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||
|
||||
import { FeeOracleManager } from "../src/v2/FeeOracleManager.sol";
|
||||
|
||||
contract FeeOracleManagerDeployer is SimpleDeployer {
|
||||
function _createContract() internal virtual override {
|
||||
new FeeOracleManager(getTornTokenAddress(), getGovernanceProxyAddress());
|
||||
}
|
||||
}
|
||||
|
||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ DEPLOY UNISWAP FEE ORACLE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||
|
||||
import { UniswapFeeOracle } from "../src/v2/UniswapFeeOracle.sol";
|
||||
|
||||
contract UniswapFeeOracleDeployer is SimpleDeployer {
|
||||
function _createContract() internal virtual override {
|
||||
new UniswapFeeOracle(getGovernanceProxyAddress(), vm.envAddress("DEPLOYED_FOM_ADDRESS"));
|
||||
}
|
||||
}
|
||||
|
||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ DEPLOY INFRASTRUCTURE UPGRADE PROPOSAL ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||
|
||||
import { InfrastructureUpgradeProposal } from "../src/proposals/InfrastructureUpgradeProposal.sol";
|
||||
|
||||
contract InfrastructureUpgradeProposalDeployer is SimpleDeployer {
|
||||
function _createContract() internal virtual override {
|
||||
new InfrastructureUpgradeProposal(
|
||||
vm.envAddress("DEPLOYED_ROUTER_ADDRESS"),
|
||||
vm.envAddress("DEPLOYED_STAKING_ADDRESS"),
|
||||
vm.envAddress("DEPLOYED_RELAYERSR_ADDRESS"),
|
||||
vm.envAddress("DEPLOYED_INSTANCESR_ADDRESS"),
|
||||
vm.envAddress("DEPLOYED_FOM_ADDRESS"),
|
||||
vm.envAddress("DEPLOYED_UNIFEO_ADDRESS")
|
||||
);
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.6.12;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
// STD imports
|
||||
|
||||
import { Script } from "forge-std/Script.sol";
|
||||
|
||||
import { console2 } from "forge-std/console2.sol";
|
||||
|
||||
// Local imports
|
||||
|
||||
import { TornadoAddresses } from "../src/common/TornadoAddresses.sol";
|
||||
|
||||
import { TornadoRouter } from "../src/v2/TornadoRouter.sol";
|
||||
|
||||
contract DeployScript is TornadoAddresses, Script {
|
||||
function run() external {
|
||||
uint256 key = vm.envUint("PRIVATE_KEY");
|
||||
|
||||
vm.startBroadcast(key);
|
||||
|
||||
new TornadoRouter(getGovernanceProxyAddress());
|
||||
|
||||
vm.stopBroadcast();
|
||||
}
|
||||
}
|
@ -54,9 +54,9 @@ contract InfrastructureUpgradeProposal {
|
||||
address payable public constant stakingProxyAddress = 0x5B3f656C80E8ddb9ec01Dd9018815576E9238c29;
|
||||
|
||||
/**
|
||||
* @notice The implementation address of the RelayerRegistry upgrade contract
|
||||
* @notice The address of the new, cleaner, better TornadoRouter
|
||||
*/
|
||||
address public immutable deployedRelayerRegistryImplementationAddress;
|
||||
address public immutable deployedTornadoRouterAddress;
|
||||
|
||||
/**
|
||||
* @notice The implementation address of the TornadoStakingRewards upgrade contract
|
||||
@ -64,9 +64,9 @@ contract InfrastructureUpgradeProposal {
|
||||
address public immutable deployedStakingRewardsImplementationAddress;
|
||||
|
||||
/**
|
||||
* @notice The implementation address of the FeeOracleManager upgrade contract
|
||||
* @notice The implementation address of the RelayerRegistry upgrade contract
|
||||
*/
|
||||
address public immutable deployedFeeOracleManagerImplementationAddress;
|
||||
address public immutable deployedRelayerRegistryImplementationAddress;
|
||||
|
||||
/**
|
||||
* @notice The implementation address of the InstanceRegistry upgrade contract
|
||||
@ -74,9 +74,9 @@ contract InfrastructureUpgradeProposal {
|
||||
address public immutable deployedInstanceRegistryImplementationAddress;
|
||||
|
||||
/**
|
||||
* @notice The address of the new, cleaner, better TornadoRouter
|
||||
* @notice The implementation address of the FeeOracleManager upgrade contract
|
||||
*/
|
||||
address public immutable deployedTornadoRouterAddress;
|
||||
address public immutable deployedFeeOracleManagerImplementationAddress;
|
||||
|
||||
/**
|
||||
* @notice This is the Uniswap Oracle which we will use for all of our traditional instances, but it will
|
||||
@ -87,19 +87,19 @@ contract InfrastructureUpgradeProposal {
|
||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LOGIC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||
|
||||
constructor(
|
||||
address _deployedUniswapFeeOracleAddress,
|
||||
address _deployedRelayerRegistryImplementationAddress,
|
||||
address _deployedTornadoRouterAddress,
|
||||
address _deployedStakingRewardsImplementationAddress,
|
||||
address _deployedFeeOracleManagerImplementationAddress,
|
||||
address _deployedRelayerRegistryImplementationAddress,
|
||||
address _deployedInstanceRegistryImplementationAddress,
|
||||
address _deployedTornadoRouterAddress
|
||||
address _deployedFeeOracleManagerImplementationAddress,
|
||||
address _deployedUniswapFeeOracleAddress
|
||||
) public {
|
||||
deployedUniswapFeeOracleAddress = _deployedUniswapFeeOracleAddress;
|
||||
deployedRelayerRegistryImplementationAddress = _deployedRelayerRegistryImplementationAddress;
|
||||
deployedStakingRewardsImplementationAddress = _deployedStakingRewardsImplementationAddress;
|
||||
deployedFeeOracleManagerImplementationAddress = _deployedFeeOracleManagerImplementationAddress;
|
||||
deployedInstanceRegistryImplementationAddress = _deployedInstanceRegistryImplementationAddress;
|
||||
deployedTornadoRouterAddress = _deployedTornadoRouterAddress;
|
||||
deployedStakingRewardsImplementationAddress = _deployedStakingRewardsImplementationAddress;
|
||||
deployedRelayerRegistryImplementationAddress = _deployedRelayerRegistryImplementationAddress;
|
||||
deployedInstanceRegistryImplementationAddress = _deployedInstanceRegistryImplementationAddress;
|
||||
deployedFeeOracleManagerImplementationAddress = _deployedFeeOracleManagerImplementationAddress;
|
||||
deployedUniswapFeeOracleAddress = _deployedUniswapFeeOracleAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -35,6 +35,8 @@ import { CurveFeeOracle, ICurvePriceOracle, CurveChainedOracles } from "src/v2/C
|
||||
|
||||
import { InfrastructureUpgradeProposal } from "src/proposals/InfrastructureUpgradeProposal.sol";
|
||||
|
||||
import { CRVUSDInstancesProposal } from "src/proposals/CRVUSDInstancesProposal.sol";
|
||||
|
||||
// Test imports
|
||||
|
||||
import { TornadoProposalTest, ProposalState } from "./TornadoProposalTest.sol";
|
||||
@ -75,6 +77,14 @@ contract Instances {
|
||||
ITornadoInstance public constant wbtc01 = ITornadoInstance(0x178169B423a011fff22B9e3F3abeA13414dDD0F1);
|
||||
ITornadoInstance public constant wbtc1 = ITornadoInstance(0x610B717796ad172B316836AC95a2ffad065CeaB4);
|
||||
ITornadoInstance public constant wbtc10 = ITornadoInstance(0xbB93e510BbCD0B7beb5A853875f9eC60275CF498);
|
||||
|
||||
/* CRVUSD instances */
|
||||
ITornadoInstance public constant cu100 = ITornadoInstance(0x913a73486Dc4AA3832A56d461542836C1eeB93be);
|
||||
ITornadoInstance public constant cu1_000 = ITornadoInstance(0x5A6b3C829dB3e938C885000c6E93CF35E74876a4);
|
||||
ITornadoInstance public constant cu10_000 = ITornadoInstance(0x49f173CDAB99a2C3800F1255393DF9B7a17B82Bb);
|
||||
ITornadoInstance public constant cu100_000 = ITornadoInstance(0x4640Dffc9fD0B113B983e3A350b070a119CA143C);
|
||||
ITornadoInstance public constant cu1_000_000 =
|
||||
ITornadoInstance(0xc4eA8Bd3Fd76f3c255395793B47F7c55aD59d991);
|
||||
}
|
||||
|
||||
contract ProposalTests is Instances, TornadoProposalTest {
|
||||
@ -155,6 +165,188 @@ contract ProposalTests is Instances, TornadoProposalTest {
|
||||
_advanceTORNETHMarket();
|
||||
}
|
||||
|
||||
function test_crvusdInstancesBasic() public {
|
||||
// Do the proposal first
|
||||
test_crvusdInstancesProposalBasic();
|
||||
|
||||
// We have to set to address this again
|
||||
_feeManagerShouldRevertAndSetFeeUpdater();
|
||||
|
||||
// Advance
|
||||
_advanceTORNETHMarket();
|
||||
|
||||
// Try to update crvusd 10000
|
||||
feeOracleManager.updateFee(cu10_000, false);
|
||||
|
||||
// Try to update all
|
||||
ITornadoInstance[] memory _toUpdate = new ITornadoInstance[](5);
|
||||
|
||||
_toUpdate[0] = cu100;
|
||||
_toUpdate[1] = cu1_000;
|
||||
_toUpdate[2] = cu10_000;
|
||||
_toUpdate[3] = cu100_000;
|
||||
_toUpdate[4] = cu1_000_000;
|
||||
|
||||
// We will need to prank an update on the uniswapFeeOracle
|
||||
// this should otherwise happen automatically though other tokens being withdrawn
|
||||
vm.startPrank(address(feeOracleManager));
|
||||
for (uint256 i = 0; i < _toUpdate.length; i++) {
|
||||
uniswapFeeOracle.update(TORN, feeOracleManager.populateInstanceWithFeeData(_toUpdate[i]));
|
||||
}
|
||||
vm.stopPrank();
|
||||
|
||||
// Now update all fees
|
||||
|
||||
uint160[] memory fees = feeOracleManager.updateFees(_toUpdate, false);
|
||||
|
||||
// Print fees to console.
|
||||
|
||||
console2.log("\n~~~~~~~~~~~~~~~~~~ LAST FEES ~~~~~~~~~~~~~~~~~~\n");
|
||||
console2.log("cu100: ", uint256(feeOracleManager.getLastFeeForInstance(cu100)));
|
||||
console2.log("cu1_000: ", uint256(feeOracleManager.getLastFeeForInstance(cu1_000)));
|
||||
console2.log("cu10_000: ", uint256(feeOracleManager.getLastFeeForInstance(cu10_000)));
|
||||
console2.log("cu100_000: ", uint256(feeOracleManager.getLastFeeForInstance(cu100_000)));
|
||||
console2.log("cu1_000_000: ", uint256(feeOracleManager.getLastFeeForInstance(cu1_000_000)));
|
||||
console2.log("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
|
||||
|
||||
delimit();
|
||||
|
||||
console2.log("\n~~~~~~~~~~~~~~~~~~ DATA ~~~~~~~~~~~~~~~~~~\n");
|
||||
InstanceState memory data = instanceRegistry.getInstanceState(cu100);
|
||||
delimit();
|
||||
console2.log("cu100:");
|
||||
console2.log("token: ", address(data.token));
|
||||
console2.log("index: ", uint256(data.index));
|
||||
console2.log("iserc20: ", data.isERC20);
|
||||
console2.log("isenabled: ", data.isEnabled);
|
||||
delimit();
|
||||
data = instanceRegistry.getInstanceState(cu1_000);
|
||||
console2.log("cu1_000:");
|
||||
console2.log("token: ", address(data.token));
|
||||
console2.log("index: ", uint256(data.index));
|
||||
console2.log("iserc20: ", data.isERC20);
|
||||
console2.log("isenabled: ", data.isEnabled);
|
||||
delimit();
|
||||
data = instanceRegistry.getInstanceState(cu10_000);
|
||||
console2.log("cu10_000:");
|
||||
console2.log("token: ", address(data.token));
|
||||
console2.log("index: ", uint256(data.index));
|
||||
console2.log("iserc20: ", data.isERC20);
|
||||
console2.log("isenabled: ", data.isEnabled);
|
||||
delimit();
|
||||
data = instanceRegistry.getInstanceState(cu100_000);
|
||||
console2.log("cu100_000:");
|
||||
console2.log("token: ", address(data.token));
|
||||
console2.log("index: ", uint256(data.index));
|
||||
console2.log("iserc20: ", data.isERC20);
|
||||
console2.log("isenabled: ", data.isEnabled);
|
||||
delimit();
|
||||
data = instanceRegistry.getInstanceState(cu1_000_000);
|
||||
console2.log("cu1_000_000:");
|
||||
console2.log("token: ", address(data.token));
|
||||
console2.log("index: ", uint256(data.index));
|
||||
console2.log("iserc20: ", data.isERC20);
|
||||
console2.log("isenabled: ", data.isEnabled);
|
||||
delimit();
|
||||
console2.log("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
|
||||
|
||||
// No access
|
||||
curveFeeOracle.update(TORN, feeOracleManager.populateInstanceWithFeeData(cu10_000));
|
||||
// No access view
|
||||
curveFeeOracle.getFee(TORN, feeOracleManager.populateInstanceWithFeeData(cu10_000));
|
||||
|
||||
// Some assertions
|
||||
require(
|
||||
strcomp(curveFeeOracle.getChainedOracleNameForInstance(cu100), "ETH/CRVUSD"),
|
||||
"oracle name doesnt match"
|
||||
);
|
||||
require(
|
||||
strcomp(
|
||||
curveFeeOracle.getChainedOracleNameForOracleHash(
|
||||
curveFeeOracle.getChainedOracleHashForInstance(cu1_000)
|
||||
),
|
||||
"ETH/CRVUSD"
|
||||
),
|
||||
"oracle name doesnt match"
|
||||
);
|
||||
|
||||
// Now since we will test updating all fees... the oracle will get updated
|
||||
// but first, warp forward
|
||||
vm.warp(now + 2 days);
|
||||
|
||||
// Should be able to update all fees
|
||||
fees = feeOracleManager.updateAllFees(true);
|
||||
feeOracleManager.version();
|
||||
|
||||
delimit();
|
||||
console2.log("\n~~~~~~~~~~~~~~~~~~ LAST FEES ~~~~~~~~~~~~~~~~~~\n");
|
||||
console2.log("cu100: ", uint256(feeOracleManager.getLastFeeForInstance(cu100)));
|
||||
console2.log("cu1_000: ", uint256(feeOracleManager.getLastFeeForInstance(cu1_000)));
|
||||
console2.log("cu10_000: ", uint256(feeOracleManager.getLastFeeForInstance(cu10_000)));
|
||||
console2.log("cu100_000: ", uint256(feeOracleManager.getLastFeeForInstance(cu100_000)));
|
||||
console2.log("cu1_000_000: ", uint256(feeOracleManager.getLastFeeForInstance(cu1_000_000)));
|
||||
console2.log("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
|
||||
delimit();
|
||||
|
||||
console2.log(
|
||||
"\n 💱 I would like to note that I have verified via calculation that post-price move, the fees work.\n Basically: (1000000*30/10000) == fee * 9 / 10^18, should be constant because block is fix. \n"
|
||||
);
|
||||
|
||||
// Just for some gas values for this call
|
||||
instanceRegistry.instanceData(cu100);
|
||||
instanceRegistry.instanceData(cu1_000);
|
||||
instanceRegistry.version();
|
||||
|
||||
router.version();
|
||||
|
||||
// Let's just check all instances one by one
|
||||
InstanceState[] memory states = instanceRegistry.getAllInstanceStates();
|
||||
|
||||
for (uint256 i = 0; i < states.length; i++) {
|
||||
if (i <= 3) {
|
||||
require(!states[i].isERC20);
|
||||
require(states[i].isEnabled);
|
||||
} else if (i <= 7) {
|
||||
require(states[i].token == DAI);
|
||||
require(states[i].isERC20);
|
||||
require(states[i].isEnabled);
|
||||
} else if (i <= 11) {
|
||||
require(states[i].token == CDAI);
|
||||
require(states[i].isERC20);
|
||||
require(states[i].isEnabled);
|
||||
} else if (i <= 13) {
|
||||
require(states[i].token == USDT);
|
||||
require(states[i].isERC20);
|
||||
require(states[i].isEnabled);
|
||||
} else if (i <= 16) {
|
||||
require(states[i].token == WBTC);
|
||||
require(states[i].isERC20);
|
||||
require(states[i].isEnabled);
|
||||
} else if (i <= 21) {
|
||||
require(states[i].token == CRVUSD);
|
||||
require(states[i].isERC20);
|
||||
require(states[i].isEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
// Now do a specific interval
|
||||
|
||||
states = instanceRegistry.getInstanceState(8, 11);
|
||||
|
||||
for (uint256 i = 0; i < 4; i++) {
|
||||
require(states[i].token == CDAI);
|
||||
require(states[i].isERC20);
|
||||
require(states[i].isEnabled);
|
||||
}
|
||||
|
||||
// Other for coverage
|
||||
require(instanceRegistry.isRegisteredInstance(cu10_000));
|
||||
|
||||
vm.prank(address(governance));
|
||||
instanceRegistry.setTornadoRouter(address(0));
|
||||
require(address(instanceRegistry.router()) == address(0));
|
||||
}
|
||||
|
||||
function test_infrastructureBasic() public {
|
||||
// Do the proposal first
|
||||
test_infrastructureUpgradeProposalBasic();
|
||||
@ -437,16 +629,34 @@ contract ProposalTests is Instances, TornadoProposalTest {
|
||||
feeOracleManager.setInstanceRegistry(address(instanceRegistry));
|
||||
}
|
||||
|
||||
function test_crvusdInstancesProposalBasic() public {
|
||||
// First pass the former proposal
|
||||
test_infrastructureUpgradeProposalBasic();
|
||||
|
||||
// Then create the crvUSD proposal
|
||||
address proposal =
|
||||
address(new CRVUSDInstancesProposal(address(curveFeeOracle), address(uniswapFeeOracle)));
|
||||
|
||||
// Propose
|
||||
uint256 id = easyPropose(proposal);
|
||||
|
||||
// Wait
|
||||
waitUntilExecutable(id);
|
||||
|
||||
// Exec
|
||||
governance.execute(id);
|
||||
}
|
||||
|
||||
function test_infrastructureUpgradeProposalBasic() public {
|
||||
// Create proposal
|
||||
address proposal = address(
|
||||
new InfrastructureUpgradeProposal(
|
||||
address(uniswapFeeOracle),
|
||||
address(implRegistry),
|
||||
address(router),
|
||||
address(implStaking),
|
||||
address(implFeeOracleManager),
|
||||
address(implRegistry),
|
||||
address(implInstanceRegistry),
|
||||
address(router)
|
||||
address(implFeeOracleManager),
|
||||
address(uniswapFeeOracle)
|
||||
)
|
||||
);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user