// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import { ProposalUtils } from "./utils/ProposalUtils.sol"; import { UpdateENSDataProposal } from "@root/UpdateENSDataProposal.sol"; import { console2 } from "@forge-std/console2.sol"; import { IENSResolver } from "@interfaces/IENSResolver.sol"; import { IENSRegistry } from "@interfaces/IENSRegistry.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "node_modules/base58-solidity/contracts/Base58.sol"; import { ENSNamehash } from "./ENSNamehash.sol"; contract TestExampleProposal is ProposalUtils { using ENSNamehash for bytes; IENSResolver internal ensResolver = IENSResolver(0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41); IENSRegistry ensRegistry = IENSRegistry(ENSAddress); modifier executeCurrentProposalBefore() { createAndExecuteProposal(); _; } function calculateDomainNode(string memory domain) internal pure returns (bytes32) { return ENSNamehash.namehash(bytes(domain)); } function calculateIpfsContenthash(string memory ipfsCid) internal pure returns (bytes memory) { if (bytes(ipfsCid).length == 0) return bytes(""); return bytes.concat(hex"e3010170", Base58.decodeFromString(ipfsCid)); } function createAndExecuteProposal() public { address proposalAddress = address(new UpdateENSDataProposal()); proposeAndExecute(proposalAddress); } struct DomainInfo { string ensName; string ipfsCid; } DomainInfo[] subdomains = [ DomainInfo("sources.tornadocash.eth", "QmQ8T782t6TDFKoZLeVeQaHabgaGjNnTGk86zKMPE4uHtx"), DomainInfo("download.sources.tornadocash.eth", "QmXzf2Lymv1YStHiiMaFrEDxkD4o4cN8cdS6N82voSaTpv"), DomainInfo("help.sources.tornadocash.eth", "QmPBfNYr7hWtpUGNbTgYzaXa6wPp5HcEysZH7pNn9zyEqe"), DomainInfo("relayers-ui.tornadocash.eth", "QmTuGF7kKRjWRjmbxg5qMox622T2VftYiZWKjRpRvYkUN6"), DomainInfo("relayers-network.tornadocash.eth", "QmTuGF7kKRjWRjmbxg5qMox622T2VftYiZWKjRpRvYkUN6"), DomainInfo("relayers-ui.sources.tornadocash.eth", "QmW535jPAscFPqh5M38YjLcwJDJjBrT6ZRtqQqCPF19SzF"), DomainInfo("relayer.sources.tornadocash.eth", "QmUcvz2A6WbLpQJ3AoGvW8auAPn2PKbfgBZAz2htfKEVLn"), DomainInfo("docs.sources.tornadocash.eth", "QmZ7tLxVF5nwmTNwq61MMf7gnUxh85t5Rs6ixxcwu1gRay"), DomainInfo("docs.tornadocash.eth", "QmUtXxMHnLGxsG3Mqm3hhQ2rByFsehSqS57LiEKpEdEiHR") ]; function testSubdomainsRegistered() public executeCurrentProposalBefore { for (uint256 i = 0; i < subdomains.length; i++) { bytes32 node = calculateDomainNode(subdomains[i].ensName); bool isRegistered = ensRegistry.recordExists(node); if (isRegistered) { console2.log("Subdomain %s registered", subdomains[i].ensName); } else { console2.log("Subdomain %s isn't registered!", subdomains[i].ensName); } require(isRegistered, "subdomain not registered"); } } function testContenthashesOnSubdomainsAreCorrect() public executeCurrentProposalBefore { for (uint256 i = 0; i < subdomains.length; i++) { bytes32 node = calculateDomainNode(subdomains[i].ensName); bytes memory desiredIpfsContenthash = calculateIpfsContenthash(subdomains[i].ipfsCid); bytes memory realContenthash = ensResolver.contenthash(node); assertEq(desiredIpfsContenthash, realContenthash, "contenthash is wrong"); } } function testOwnerOfAllSubdomainsIsGovernance() public executeCurrentProposalBefore { for (uint256 i = 0; i < subdomains.length; i++) { bytes32 node = calculateDomainNode(subdomains[i].ensName); require(ensRegistry.owner(node) == governanceAddress, "owner is not governance"); } } function testUsdtTransferredToDeveloper() public { bytes32 tornadoDeveloperENSNode = calculateDomainNode("tornado-dev.eth"); address tornadoDeveloperAddress = IENSResolver(ensRegistry.resolver(tornadoDeveloperENSNode)).addr(tornadoDeveloperENSNode); address usdtTokenAddress = 0xdAC17F958D2ee523a2206206994597C13D831ec7; uint256 usdtDecimals = 1e6; IERC20 usdt = IERC20(usdtTokenAddress); uint256 developerBalanceBeforeProposal = usdt.balanceOf(tornadoDeveloperAddress); uint256 tornContractBalanceBeforeProposal = usdt.balanceOf(tornTokenAddress); console2.log("Torn contract usdt balance before proposal execution: %s USDT", tornContractBalanceBeforeProposal / usdtDecimals); console2.log("Usdt balance on developer address before proposal execution: %s", developerBalanceBeforeProposal / usdtDecimals); require(developerBalanceBeforeProposal == 0 && tornContractBalanceBeforeProposal > 1500, "wrong usdt balance before proposal"); createAndExecuteProposal(); uint256 developerBalanceAfterProposal = usdt.balanceOf(tornadoDeveloperAddress); uint256 tornContractBalanceAfterProposal = usdt.balanceOf(tornTokenAddress); console2.log("Torn contract usdt balance after proposal exectuion: %s USDT", tornContractBalanceAfterProposal / usdtDecimals); console2.log("Usdt balance on developer address after proposal execution: %s", developerBalanceAfterProposal / usdtDecimals); require( developerBalanceAfterProposal == tornContractBalanceBeforeProposal && tornContractBalanceAfterProposal == 0, "wrong usdt balance after proposal" ); } }