diff --git a/hardhat.config.ts b/hardhat.config.ts index 7e432cf..93a04d6 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -1,18 +1,22 @@ -import { HardhatUserConfig } from 'hardhat/config'; -import { networkConfig } from './utils/config-loader'; -import * as dotenv from 'dotenv'; +import { HardhatUserConfig } from 'hardhat/config' +import { networkConfig } from './utils/config-loader' -import '@nomiclabs/hardhat-truffle5'; -import '@nomiclabs/hardhat-ethers'; -import '@nomiclabs/hardhat-web3'; -import '@nomiclabs/hardhat-etherscan'; -import '@nomicfoundation/hardhat-chai-matchers'; +import '@nomiclabs/hardhat-truffle5' +import '@nomiclabs/hardhat-ethers' +import '@nomiclabs/hardhat-web3' +import '@nomiclabs/hardhat-etherscan' +import '@nomicfoundation/hardhat-chai-matchers' -import 'hardhat-gas-reporter'; -import 'solidity-coverage'; +import 'hardhat-gas-reporter' +import 'solidity-coverage' +import { HardhatConfig } from 'hardhat/types' -dotenv.config(); -loadAndValidateEnvironment(); +require('dotenv').config(); + +const ganacheNetwork = { + url: 'http://127.0.0.1:8545', + blockGasLimit: 6000000000 +} const config: HardhatUserConfig = { solidity: { @@ -29,36 +33,68 @@ const config: HardhatUserConfig = { }, paths: { root: 'src', - tests: 'tests' + tests: '../tests' }, networks: { - // Define here to easily specify private keys - localhost: { - url: 'http://127.0.0.1:8545', - accounts: [process.env.DEPLOYER_PRIV_KEY!, process.env.WALLET_IMPL_CHANGER_PRIV_KEY!] + hardhat: { + chainId: 13473, + forking: { + url: "https://rpc.testnet.immutable.com/", + }, }, - devnet: { - url: 'https://rpc.dev.immutable.com', - accounts: [process.env.DEPLOYER_PRIV_KEY!, process.env.WALLET_IMPL_CHANGER_PRIV_KEY!] + fork: { + url: "http://127.0.0.1:8545/" }, - testnet: { - url: 'https://rpc.testnet.immutable.com', - accounts: [process.env.DEPLOYER_PRIV_KEY!, process.env.WALLET_IMPL_CHANGER_PRIV_KEY!] + // Define here to easily specify private keys + zkevm: validateEnvironment() ? { + url: "https://rpc.testnet.immutable.com/", + accounts: ["1f6f17db77bf966ae1bb2fa0fc32868a3d5913f1b931f085ffe6522d5966f8d3"] + } : { + url: "SET ENVIRONMENT VARIABLES", + accounts: [], }, - mainnet: { - url: 'https://rpc.immutable.com', - accounts: [process.env.DEPLOYER_PRIV_KEY!, process.env.WALLET_IMPL_CHANGER_PRIV_KEY!] + zkevmdevnet: { + url: "0xEB7FFb9fb0c80437120f6F97EdE60aB59055EAE0", + accounts: [] }, + sepolia: networkConfig('sepolia'), + mainnet: networkConfig('mainnet'), + ropsten: networkConfig('ropsten'), + rinkeby: networkConfig('rinkeby'), + kovan: networkConfig('kovan'), + goerli: networkConfig('goerli'), + matic: networkConfig('matic'), + mumbai: networkConfig('mumbai'), + arbitrum: networkConfig('arbitrum'), + arbitrumTestnet: networkConfig('arbitrum-testnet'), + optimism: networkConfig('optimism'), + metis: networkConfig('metis'), + nova: networkConfig('nova'), + avalanche: networkConfig('avalanche'), + avalancheTestnet: networkConfig('avalanche-testnet'), + ganache: ganacheNetwork, + coverage: { + url: 'http://localhost:8555' + } + }, + etherscan: { + // Your API key for Etherscan + // Obtain one at https://etherscan.io/ + apiKey: networkConfig('mainnet').etherscan }, mocha: { timeout: process.env.COVERAGE ? 15 * 60 * 1000 : 30 * 1000 }, -}; + gasReporter: { + enabled: !!process.env.REPORT_GAS === true, + currency: 'USD', + gasPrice: 21, + showTimeSpent: true + }, +} -export default config; +export default config -function loadAndValidateEnvironment(): boolean { - return !!process.env.DEPLOYER_PRIV_KEY && - !!process.env.WALLET_IMPL_CHANGER_PRIV_KEY && - !!process.env.DEPLOYER_CONTRACT_ADDRESS; -} +function validateEnvironment(): boolean { + return !!process.env.DEPLOYER_PRIV_KEY && !!process.env.WALLET_IMPL_CHANGER_PRIV_KEY +} \ No newline at end of file diff --git a/src/contracts/modules/MainModuleDynamicAuthLog.sol b/src/contracts/modules/MainModuleDynamicAuthLog.sol new file mode 100644 index 0000000..29eebd3 --- /dev/null +++ b/src/contracts/modules/MainModuleDynamicAuthLog.sol @@ -0,0 +1,52 @@ +// Copyright Immutable Pty Ltd 2018 - 2023 +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.17; + +import "./commons/ModuleAuthDynamic.sol"; +import "./commons/ModuleReceivers.sol"; +import "./commons/ModuleCalls.sol"; +import "./commons/ModuleUpdate.sol"; + + +/** + * TODO Peter update docs + * @notice Contains the core functionality arcadeum wallets will inherit with + * the added functionality that the main-module can be changed. + * @dev If using a new main module, developpers must ensure that all inherited + * contracts by the mainmodule don't conflict and are accounted for to be + * supported by the supportsInterface method. + */ +contract MainModuleDynamicAuthLog is + ModuleAuthDynamic, + ModuleCalls, + ModuleReceivers, + ModuleUpdate +{ + + // solhint-disable-next-line no-empty-blocks + constructor(address _factory, address _startup) ModuleAuthDynamic (_factory, _startup) { } + + + /** + * @notice Query if a contract implements an interface + * @param _interfaceID The interface identifier, as specified in ERC-165 + * @dev If using a new main module, developpers must ensure that all inherited + * contracts by the mainmodule don't conflict and are accounted for to be + * supported by the supportsInterface method. + * @return `true` if the contract implements `_interfaceID` + */ + function supportsInterface( + bytes4 _interfaceID + ) public override( + ModuleAuthUpgradable, + ModuleCalls, + ModuleReceivers, + ModuleUpdate + ) pure returns (bool) { + return super.supportsInterface(_interfaceID); + } + + function version() external pure virtual returns (uint256) { + return 1; + } +} diff --git a/src/contracts/modules/commons/ModuleAuth.sol b/src/contracts/modules/commons/ModuleAuth.sol index 25cdc1d..2f736be 100644 --- a/src/contracts/modules/commons/ModuleAuth.sol +++ b/src/contracts/modules/commons/ModuleAuth.sol @@ -9,6 +9,8 @@ import "./interfaces/IModuleAuth.sol"; import "./ModuleERC165.sol"; +import "hardhat/console.sol"; + abstract contract ModuleAuth is IModuleAuth, ModuleERC165, SignatureValidator, IERC1271Wallet { using LibBytes for bytes; @@ -101,7 +103,6 @@ abstract contract ModuleAuth is IModuleAuth, ModuleERC165, SignatureValidator, I bytes memory signature; (signature, rindex) = _signature.readBytes66(rindex); addr = recoverSigner(_hash, signature); - // Acumulate total weight of the signature totalWeight += addrWeight; } else if (flag == FLAG_DYNAMIC_SIGNATURE) { @@ -115,7 +116,9 @@ abstract contract ModuleAuth is IModuleAuth, ModuleERC165, SignatureValidator, I // Read dynamic size signature bytes memory signature; (signature, rindex) = _signature.readBytes(rindex, size); - require(isValidSignature(_hash, addr, signature), "ModuleAuth#_signatureValidation: INVALID_SIGNATURE"); + console.log("ModuleAuth: isValidSignature"); + console.logBool(isValidSignature(_hash, addr, signature)); + require(isValidSignature(_hash, addr, signature), "ModuleAuth#_signatureValidation: INVALID_SIGNATURE"); // Acumulate total weight of the signature totalWeight += addrWeight; @@ -124,10 +127,15 @@ abstract contract ModuleAuth is IModuleAuth, ModuleERC165, SignatureValidator, I } // Write weight and address to image + console.log("Calculating image hash for %s with weight %s", addr, addrWeight); imageHash = keccak256(abi.encode(imageHash, addrWeight, addr)); } - + (bool verified, bool needsUpdate) = _isValidImage(imageHash); + console.log("Verified"); + console.logBool(verified); + console.log("Needs update"); + console.logBool(needsUpdate); return ((totalWeight >= threshold && verified), needsUpdate, imageHash); } @@ -166,7 +174,7 @@ abstract contract ModuleAuth is IModuleAuth, ModuleERC165, SignatureValidator, I // solhint-disable-next-line no-empty-blocks function updateImageHashInternal(bytes32 _imageHash) internal virtual { // Default implementation does nothing - } +} @@ -183,6 +191,7 @@ abstract contract ModuleAuth is IModuleAuth, ModuleERC165, SignatureValidator, I bytes calldata _data, bytes calldata _signatures ) external override view returns (bytes4) { + console.log("====== IS VALID SIGNATURE ======"); // Validate signatures if (_signatureValidationInternal(_subDigest(keccak256(_data)), _signatures)) { return SELECTOR_ERC1271_BYTES_BYTES; @@ -203,6 +212,7 @@ abstract contract ModuleAuth is IModuleAuth, ModuleERC165, SignatureValidator, I bytes32 _hash, bytes calldata _signatures ) external override view returns (bytes4) { + console.log("====== IS VALID SIGNATURE bytes32 HASH ======"); // Validate signatures if (_signatureValidationInternal(_subDigest(_hash), _signatures)) { return SELECTOR_ERC1271_BYTES32_BYTES; diff --git a/src/contracts/modules/commons/ModuleAuthDynamic.sol b/src/contracts/modules/commons/ModuleAuthDynamic.sol index 505c4ed..5866842 100644 --- a/src/contracts/modules/commons/ModuleAuthDynamic.sol +++ b/src/contracts/modules/commons/ModuleAuthDynamic.sol @@ -28,7 +28,48 @@ abstract contract ModuleAuthDynamic is ModuleAuthUpgradable { * @return true if the signature image is valid, and true if the image hash needs to be updated */ function _isValidImage(bytes32 _imageHash) internal view override returns (bool, bool) { + console.log("IS VALID IMAGE 1:"); + console.logBytes32(_imageHash); + console.log("GOT %s", address( + uint160(uint256( + keccak256( + abi.encodePacked( + bytes1(0xff), + FACTORY, + _imageHash, + INIT_CODE_HASH + ) + ) + )) + )); + console.log("WANT %s", address(this)); bytes32 storedImageHash = ModuleStorage.readBytes32(ImageHashKey.IMAGE_HASH_KEY); + console.log("WANT (calculate) %s", address( + uint160(uint256( + keccak256( + abi.encodePacked( + bytes1(0xff), + FACTORY, + storedImageHash, + INIT_CODE_HASH + ) + ) + )) + )); + console.log("STORED IMAGE HASH: "); + console.logBytes32(storedImageHash); + address( + uint160(uint256( + keccak256( + abi.encodePacked( + bytes1(0xff), + FACTORY, + _imageHash, + INIT_CODE_HASH + ) + ) + )) + ); if (storedImageHash == 0) { // No image hash stored. Check that the image hash was used as the salt when // deploying the wallet proxy contract. @@ -47,7 +88,7 @@ abstract contract ModuleAuthDynamic is ModuleAuthUpgradable { // Indicate need to update = true. This will trigger a call to store the image hash return (authenticated, true); } - + // Image hash has been stored. return ((_imageHash != bytes32(0) && _imageHash == storedImageHash), false); } diff --git a/tests/EIP712Debug.ts b/tests/EIP712Debug.ts new file mode 100644 index 0000000..438b39b --- /dev/null +++ b/tests/EIP712Debug.ts @@ -0,0 +1,85 @@ +import { ethers, network } from 'hardhat' + +// Fork network @ https://rpc.testnet.immutable.com/ +// Attach contract at LatestWalletImplLocator +// Get MainModuleDynamicAuth address +// Deploy MainModuleDynamicAuth (with console logs) +// Get code at the newly deployed MainModuleDynamicAuth (w/ logs) +// Set the code at the MainModuleDynamicAuth to be the newly deployed MainModuleDynamicAuth (w/ logs) +// Do signature verification +// inspect console.log lines + +// "passport": { +// "zkevm_eth_address": "0x3878cadc6a521dceb1f46599913ce726c430a8e1", +// "zkevm_user_admin_address": "0xa110e9ccdc3714d1dabb2f25e8883061f75011bd" + +async function main() { + // Get signature values + const typedHash = '0x0b8f209be8d541a4ded6b82c0414aac2cee9cb89f19518b6ee1502ba555cb16c' + const messageSubDigest = '0xede3e129db20579573bccededc094e9a0f3cb8c8df59bbf3526eb7072bffa6f3' + const packedAggregateSignature = + '0x000202011b1d383526a2815d26550eb314b5d7e0551327330043c9d1d1d25201bd592da3eb99a5c4568105a79c168b93eebe2444ddf1f7a61174394b2b8616ba8ce9aae7741e2131caf66b80773f3557e18ec0d93a68a17090cb1b010300014f84dcc8d9fe6c2d8ed83d2edc01cc1fc81e29a6a75bce6301072b3e30f972b744f259055466795f372eb5d82c5314a781209c827c634fd3435d617ce58639481c02' + const relayerSig = + '0xc9d1d1d25201bd592da3eb99a5c4568105a79c168b93eebe2444ddf1f7a61174394b2b8616ba8ce9aae7741e2131caf66b80773f3557e18ec0d93a68a17090cb1b01' + + // Recover signer +// const signer = await ethers.getContractFactory('SignerCheck') +// const signerDeployed = await signer.deploy() +// await signerDeployed.deployed() +// const signerRet = await signerDeployed.recover(messageSubDigest, relayerSig) + + // Verify supplied signature against Immutable signer +// const immutableSignerAddr = '0x1B1D383526A2815d26550eb314B5d7e055132733' +// const immutableSigner = await ethers.getContractAt('ImmutableSigner', immutableSignerAddr) +// const immutableSignerPubKey = await immutableSigner.primarySigner() + +// // Verify returned signer against Immutable signer +// if (signerRet !== immutableSignerPubKey) { +// console.log('Signer mismatch') +// process.exit(0) +// } + + // Set addresses + const scwAddr = '0x3878cadc6a521dceb1f46599913ce726c430a8e1' + const factoryAddr = '0x55b9d1cd803d5acA8ea23ccd96f6a756DED9f5a9' + const startupAddr = '0x8df826438e652f7124fe07F413fA3556cd57edB5' + const walletImplLocatorAddr = '0x657d339b8616033fee25f66ea1d00c3f30b14171' + + // Get MainModuleDynamicAuth address + const walletImplLocator = await ethers.getContractAt('LatestWalletImplLocator', walletImplLocatorAddr) + const mainModuleAddr = await walletImplLocator.latestWalletImplementation() + + // Deploy new MainModuleDynamicAuthLog + const ModuleLog = await ethers.getContractFactory('MainModuleDynamicAuthLog') + const moduleLog = await ModuleLog.deploy(factoryAddr, startupAddr) + await moduleLog.deployed() + + // Get code for logging + const modLogCode = await ethers.provider.getCode(moduleLog.address) + + // Get address for main module + const walletProxy = await ethers.getContractAt('IWalletProxy', scwAddr) + + // Set code + // await network.provider.send('hardhat_setCode', [mainModuleAddr, modLogCode]) + // await network.provider.send('hardhat_mine', ["0x100"]) +// console.log('MAIN MODULE ADDDR: ', moduleLog.address) + console.log("STORAGE AT: ", await ethers.provider.getStorageAt(scwAddr, scwAddr)); + console.log('PROXY IMPLEMENTATION: ', await walletProxy.PROXY_getImplementation()) + const implementationValue = "0x000000000000000000000000" + moduleLog.address.substring(2); + await network.provider.send('hardhat_setStorageAt', [scwAddr, scwAddr, implementationValue]) + + + // Do signature verification + // Attatch to SCW + const moduleAuth = await ethers.getContractAt('ModuleAuth', scwAddr) + // Verify 2-of-2 signature + const magicValue = await moduleAuth['isValidSignature(bytes32,bytes)'](typedHash, packedAggregateSignature) + + console.log('MAGIC VALUE', magicValue) +} + +main().catch(error => { + console.error(error) + process.exit(1) +}) diff --git a/tests/EIP712Debug2.ts b/tests/EIP712Debug2.ts new file mode 100644 index 0000000..3a41a43 --- /dev/null +++ b/tests/EIP712Debug2.ts @@ -0,0 +1,85 @@ +import { ethers, network } from 'hardhat' + +// Fork network @ https://rpc.testnet.immutable.com/ +// Attach contract at LatestWalletImplLocator +// Get MainModuleDynamicAuth address +// Deploy MainModuleDynamicAuth (with console logs) +// Get code at the newly deployed MainModuleDynamicAuth (w/ logs) +// Set the code at the MainModuleDynamicAuth to be the newly deployed MainModuleDynamicAuth (w/ logs) +// Do signature verification +// inspect console.log lines + +// "passport": { +// "zkevm_eth_address": "0x3878cadc6a521dceb1f46599913ce726c430a8e1", +// "zkevm_user_admin_address": "0xa110e9ccdc3714d1dabb2f25e8883061f75011bd" + +async function main() { + // Get signature values + const typedHash = '0xe8a936e6c05a3cfea78c9df4bbcec02a3253d7358ba15b42f78aba327e25c180' + const messageSubDigest = '0xede3e129db20579573bccededc094e9a0f3cb8c8df59bbf3526eb7072bffa6f3' + const packedAggregateSignature = + '0x000202011b1d383526a2815d26550eb314b5d7e0551327330043a9b3a8608b3f49eaeedaa13f60967982a8b40abe3203f027787b125b273f780f669b07583967f86e2ad0f4100094b91586b5d89bde8e6fcd0236f5ece8ffd4391b01030001294c4e19c04efff53c34ed7a2997bb9161b0c7f33db3af1148c66eb63d6593a668bb0da9bd32fcc3bbed426d515dff22886245a3583d2e275add28418ca2c63e1b02' + const relayerSig = + '0xc9d1d1d25201bd592da3eb99a5c4568105a79c168b93eebe2444ddf1f7a61174394b2b8616ba8ce9aae7741e2131caf66b80773f3557e18ec0d93a68a17090cb1b01' + + // Recover signer +// const signer = await ethers.getContractFactory('SignerCheck') +// const signerDeployed = await signer.deploy() +// await signerDeployed.deployed() +// const signerRet = await signerDeployed.recover(messageSubDigest, relayerSig) + + // Verify supplied signature against Immutable signer +// const immutableSignerAddr = '0x1B1D383526A2815d26550eb314B5d7e055132733' +// const immutableSigner = await ethers.getContractAt('ImmutableSigner', immutableSignerAddr) +// const immutableSignerPubKey = await immutableSigner.primarySigner() + +// // Verify returned signer against Immutable signer +// if (signerRet !== immutableSignerPubKey) { +// console.log('Signer mismatch') +// process.exit(0) +// } + + // Set addresses + const scwAddr = '0x0aa0bbd0bde831fd2288bc397b6b76f73d960650' + const factoryAddr = '0x55b9d1cd803d5acA8ea23ccd96f6a756DED9f5a9' + const startupAddr = '0x8df826438e652f7124fe07F413fA3556cd57edB5' + const walletImplLocatorAddr = '0x657d339b8616033fee25f66ea1d00c3f30b14171' + + // Get MainModuleDynamicAuth address + const walletImplLocator = await ethers.getContractAt('LatestWalletImplLocator', walletImplLocatorAddr) + const mainModuleAddr = await walletImplLocator.latestWalletImplementation() + + // Deploy new MainModuleDynamicAuthLog + const ModuleLog = await ethers.getContractFactory('MainModuleDynamicAuthLog') + const moduleLog = await ModuleLog.deploy(factoryAddr, startupAddr) + await moduleLog.deployed() + + // Get code for logging + const modLogCode = await ethers.provider.getCode(moduleLog.address) + + // Get address for main module + const walletProxy = await ethers.getContractAt('IWalletProxy', scwAddr) + + // Set code + // await network.provider.send('hardhat_setCode', [mainModuleAddr, modLogCode]) + // await network.provider.send('hardhat_mine', ["0x100"]) +// console.log('MAIN MODULE ADDDR: ', moduleLog.address) + console.log("STORAGE AT: ", await ethers.provider.getStorageAt(scwAddr, scwAddr)); + console.log('PROXY IMPLEMENTATION: ', await walletProxy.PROXY_getImplementation()) + const implementationValue = "0x000000000000000000000000" + moduleLog.address.substring(2); + await network.provider.send('hardhat_setStorageAt', [scwAddr, "0xaa0bbd0bde831fd2288bc397b6b76f73d960650", implementationValue]) + + + // Do signature verification + // Attatch to SCW + const moduleAuth = await ethers.getContractAt('ModuleAuth', scwAddr) + // Verify 2-of-2 signature + const magicValue = await moduleAuth['isValidSignature(bytes32,bytes)'](typedHash, packedAggregateSignature) + + console.log('MAGIC VALUE', magicValue) +} + +main().catch(error => { + console.error(error) + process.exit(1) +}) diff --git a/utils/config-loader.ts b/utils/config-loader.ts index d59135e..20942fd 100644 --- a/utils/config-loader.ts +++ b/utils/config-loader.ts @@ -10,7 +10,7 @@ export const getEnvConfig = (env: string) => { const envLoad = dotenv.config({ path: envFile }) if (envLoad.error) { - console.warn('No config found, using default') + // console.warn('No config found, using default') return { ETH_MNEMONIC: ethers.Wallet.createRandom().mnemonic.phrase } }