diff --git a/packages/core/config/local.ts b/packages/core/config/local.ts deleted file mode 100644 index 5b094bd1c..000000000 --- a/packages/core/config/local.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { SphinxSystemConfig } from '../src' - -const config: SphinxSystemConfig = { - relayers: ['0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'], -} - -export default config diff --git a/packages/core/config/standard.ts b/packages/core/config/standard.ts deleted file mode 100644 index 56ae581f1..000000000 --- a/packages/core/config/standard.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { SphinxSystemConfig } from '../src' - -const config: SphinxSystemConfig = { - relayers: [ - '0x42761FAcF5e6091fcA0e38F450adfB1E22bD8c3C', - '0x4F2107d09B095B92f80ecd5b66C4004B87DC2652', - '0x791Cf9e43E0ca66b470E2a82Ec103d9e712623e2', - '0xC034550B542b83BA1De312b21d1C94a9a52B1595', - '0x808923399391944164220074Ef3Cc6ad4701526f', - '0xb7e97060DE2DFfDcB39d765079A3ddd07d6E30A2', - ], -} - -export default config diff --git a/packages/core/hardhat.config.ts b/packages/core/hardhat.config.ts index cc6ce468d..cfb394301 100644 --- a/packages/core/hardhat.config.ts +++ b/packages/core/hardhat.config.ts @@ -14,7 +14,7 @@ import { Wallet } from 'ethers' import ora from 'ora' import { SphinxJsonRpcProvider } from './src/provider' -import { SphinxSystemConfig, deploySphinxSystem } from './src/languages' +import { deploySphinxSystem } from './src/languages' import { verifySphinxSystem } from './src/etherscan' import { ExecutionMode } from './src/constants' import { isVerificationSupportedForNetwork } from './src/networks' @@ -61,49 +61,38 @@ const config: HardhatUserConfig = { task('deploy-system') .setDescription('Deploys the Sphinx contracts to the specified network') - .addParam('systemConfig', 'Path to a Sphinx system config file') - .setAction( - async ( - args: { - systemConfig: string - }, - hre: HardhatRuntimeEnvironment - ) => { - // Throw an error if we're on the Hardhat network. This ensures that the `url` field is - // defined for this network. - if (!('url' in hre.network.config)) { - throw new Error( - `Cannot deploy Sphinx on the Hardhat network using this task.` - ) - } - const provider = new SphinxJsonRpcProvider(hre.network.config.url) - const signer = new Wallet(process.env.PRIVATE_KEY!).connect(provider) - - const systemConfig: SphinxSystemConfig = - require(args.systemConfig).default + .setAction(async (_, hre: HardhatRuntimeEnvironment) => { + // Throw an error if we're on the Hardhat network. This ensures that the `url` field is + // defined for this network. + if (!('url' in hre.network.config)) { + throw new Error( + `Cannot deploy Sphinx on the Hardhat network using this task.` + ) + } + const provider = new SphinxJsonRpcProvider(hre.network.config.url) + const signer = new Wallet(process.env.PRIVATE_KEY!).connect(provider) - const spinner = ora() - const logger = new Logger({ - name: 'Logger', - }) + const spinner = ora() + const logger = new Logger({ + name: 'Logger', + }) - await deploySphinxSystem( - provider, - signer, - systemConfig.relayers, - ExecutionMode.LiveNetworkCLI, - true, - spinner - ) + await deploySphinxSystem( + provider, + signer, + [], + ExecutionMode.LiveNetworkCLI, + true, + spinner + ) - if ( - isVerificationSupportedForNetwork((await provider.getNetwork()).chainId) - ) { - await verifySphinxSystem(provider, logger) - } else { - spinner.info('Verification unsupported on this network') - } + if ( + isVerificationSupportedForNetwork((await provider.getNetwork()).chainId) + ) { + await verifySphinxSystem(provider, logger) + } else { + spinner.info('Verification unsupported on this network') } - ) + }) export default config diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index f8a360174..efe13d3e4 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -374,9 +374,13 @@ export const getDuplicateElements = (arr: Array): Array => { } export const fetchSphinxManagedBaseUrl = () => { - return process.env.SPHINX_MANAGED_BASE_URL - ? process.env.SPHINX_MANAGED_BASE_URL - : 'https://www.sphinx.dev' + if (process.env.SPHINX_MANAGED_BASE_URL) { + return process.env.SPHINX_MANAGED_BASE_URL + } else { + throw new Error( + 'You must define a SPHINX_MANAGED_BASE_URL environment variable pointing to your Sphinx instance.' + ) + } } export const readSphinxLock = async (): Promise => { diff --git a/packages/plugins/src/cli/propose/index.ts b/packages/plugins/src/cli/propose/index.ts index e68ac736b..360bb1e75 100644 --- a/packages/plugins/src/cli/propose/index.ts +++ b/packages/plugins/src/cli/propose/index.ts @@ -308,8 +308,7 @@ export const propose = async ( networks, testnets, mainnets, - foundryToml.rpcEndpoints, - sphinxContext.isLiveNetwork + foundryToml.rpcEndpoints ) spinner.succeed(`Validated networks.`) diff --git a/packages/plugins/src/foundry/utils/index.ts b/packages/plugins/src/foundry/utils/index.ts index da27ae14e..f367ea4bb 100644 --- a/packages/plugins/src/foundry/utils/index.ts +++ b/packages/plugins/src/foundry/utils/index.ts @@ -82,14 +82,12 @@ import { import { AssertNoLinkedLibraries } from '../../cli/types' import { BuildInfoTemplate, trimObjectToType } from './trim' import { assertValidNodeVersion } from '../../cli/utils' -import { SphinxContext } from '../../cli/context' import { InvalidFirstSigArgumentErrorMessage, SigCalledWithNoArgsErrorMessage, SphinxConfigMainnetsContainsTestnetsErrorMessage, SphinxConfigTestnetsContainsMainnetsErrorMessage, getFailedRequestErrorMessage, - getLocalNetworkErrorMessage, getMissingEndpointErrorMessage, getMixedNetworkTypeErrorMessage, getUnsupportedNetworkErrorMessage, @@ -1497,8 +1495,7 @@ export const validateProposalNetworks = async ( cliNetworks: Array, configTestnets: Array, configMainnets: Array, - rpcEndpoints: FoundryToml['rpcEndpoints'], - isLiveNetwork: SphinxContext['isLiveNetwork'] + rpcEndpoints: FoundryToml['rpcEndpoints'] ): Promise<{ rpcUrls: Array; isTestnet: boolean }> => { if (cliNetworks.length === 0) { throw new Error(`Expected at least one network, but none were supplied.`) @@ -1552,12 +1549,6 @@ export const validateProposalNetworks = async ( } } - if (!(await isLiveNetwork(provider))) { - if (process.env.SPHINX_INTERNAL__ALLOW_LOCAL_NODES !== 'true') { - return { type: 'localNetwork', network } - } - } - return { type: 'valid', rpcUrl, @@ -1624,12 +1615,6 @@ export const validateProposalNetworks = async ( throw new Error(getUnsupportedNetworkErrorMessage(unsupported)) } - if (localNetwork.length > 0) { - if (process.env.SPHINX_INTERNAL__ALLOW_LOCAL_NODES !== 'true') { - throw new Error(getLocalNetworkErrorMessage(localNetwork)) - } - } - // Check if the array contains a mix of test networks and production networks. We check this after // resolving the promises above because we need to know all of the network types. const networkTypes = valid.map(({ networkType }) => networkType) diff --git a/packages/plugins/test/mocha/cli/propose.spec.ts b/packages/plugins/test/mocha/cli/propose.spec.ts index 3dce855df..75cd6cf55 100644 --- a/packages/plugins/test/mocha/cli/propose.spec.ts +++ b/packages/plugins/test/mocha/cli/propose.spec.ts @@ -3,7 +3,6 @@ import chaiAsPromised from 'chai-as-promised' import { Create2ActionInput, ExecutionMode, - NetworkConfig, ProposalRequest, SphinxJsonRpcProvider, SphinxPreview, @@ -11,7 +10,6 @@ import { execAsync, fetchChainIdForNetwork, getSphinxWalletPrivateKey, - isLiveNetwork, } from '@sphinx-labs/core' import { ethers } from 'ethers' import { DETERMINISTIC_DEPLOYMENT_PROXY_ADDRESS } from '@sphinx-labs/contracts' @@ -19,7 +17,6 @@ import { DETERMINISTIC_DEPLOYMENT_PROXY_ADDRESS } from '@sphinx-labs/contracts' import * as MyContract2Artifact from '../../../out/artifacts/MyContracts.sol/MyContract2.json' import * as MyLargeContractArtifact from '../../../out/artifacts/MyContracts.sol/MyLargeContract.json' import * as RevertDuringSimulation from '../../../out/artifacts/RevertDuringSimulation.sol/RevertDuringSimulation.json' -import * as Owned from '../../../out/artifacts/Owned.sol/Owned.json' import { propose } from '../../../src/cli/propose' import { deploy } from '../../../src/cli/deploy' import { makeMockSphinxContextForIntegrationTests } from '../mock' @@ -29,7 +26,6 @@ import { getSphinxModuleAddressFromScript, getAnvilRpcUrl, } from '../common' -import { SphinxContext } from '../../../src/cli/context' chai.use(chaiAsPromised) const expect = chai.expect @@ -182,8 +178,7 @@ describe('Propose CLI command', () => { chainId: '11155111', safeAddress: proposalRequest.safeAddress, }, - ], - networkConfigArray + ] ) // Check that the DeploymentConfig array contains a contract with the correct address. @@ -192,15 +187,6 @@ describe('Propose CLI command', () => { expect( (networkConfig.actionInputs[0] as Create2ActionInput).create2Address ).equals(expectedContractAddress) - - await assertValidGasEstimates( - proposalRequest.gasEstimates, - networkConfigArray, - networks, - scriptPath, - targetContract, - context - ) }) it('Proposes without preview using --mainnets', async () => { @@ -328,8 +314,7 @@ describe('Propose CLI command', () => { chainId: '10', safeAddress: proposalRequest.safeAddress, }, - ], - networkConfigArray + ] ) // Check that the DeploymentConfig array contains contracts with the correct addresses. @@ -341,15 +326,6 @@ describe('Propose CLI command', () => { expect( (optimismConfig.actionInputs[0] as Create2ActionInput).create2Address ).equals(expectedContractAddressOptimism) - - await assertValidGasEstimates( - proposalRequest.gasEstimates, - networkConfigArray, - networks, - scriptPath, - targetContract, - context - ) }) // We'll propose a script that deploys a contract near the contract size limit. We'll deploy it @@ -445,8 +421,7 @@ describe('Propose CLI command', () => { chainId: '11155111', safeAddress: proposalRequest.safeAddress, }, - ], - networkConfigArray + ] ) // Check that the DeploymentConfig array contains contracts with the correct addresses. @@ -457,16 +432,6 @@ describe('Propose CLI command', () => { (networkConfig.actionInputs[i] as Create2ActionInput).create2Address ).equals(expectedContractAddresses[i]) } - - await assertValidGasEstimates( - proposalRequest.gasEstimates, - networkConfigArray, - networks, - scriptPath, - undefined, - context, - sig - ) }) it('Dry runs for a Gnosis Safe and Sphinx Module that have already executed a deployment', async () => { @@ -552,8 +517,7 @@ describe('Propose CLI command', () => { chainId: '11155111', safeAddress: proposalRequest.safeAddress, }, - ], - networkConfigArray + ] ) // Check that the DeploymentConfig array contains a contract with the correct address. @@ -562,15 +526,6 @@ describe('Propose CLI command', () => { expect( (networkConfig.actionInputs[0] as Create2ActionInput).create2Address ).equals(expectedContractAddress) - - await assertValidGasEstimates( - proposalRequest.gasEstimates, - networkConfigArray, - networks, - scriptPath, - targetContract, - context - ) }) // We exit early even if the Gnosis Safe and Sphinx Module haven't been deployed yet. In other @@ -681,8 +636,7 @@ describe('Propose CLI command', () => { chainId: '10', safeAddress: proposalRequest.safeAddress, }, - ], - networkConfigArray + ] ) // Check that the DeploymentConfig array contains a contract with the correct address. @@ -693,15 +647,6 @@ describe('Propose CLI command', () => { ).equals(expectedContractAddress) const optimismConfig = networkConfigArray[1] expect(optimismConfig.actionInputs.length).equals(0) - - await assertValidGasEstimates( - proposalRequest.gasEstimates, - networkConfigArray, - networks, - scriptPath, - undefined, - context - ) }) // This test checks that the proposal simulation can fail after the transactions have been @@ -753,224 +698,18 @@ describe('Propose CLI command', () => { expect(errorThrown).to.be.true }) - - describe('Issues', () => { - // We reuse the CHU-676 test to cover CHU-760 since they don't conflict with each other - describe('CHU-760: Can propose and deploy against local node with SPHINX_INTERNAL__ALLOW_LOCAL_NODES env variable', async () => { - let context: SphinxContext - - beforeEach(async () => { - // Store the original environment variables. We'll reset them after this test suite is finished. - originalEnv = { ...process.env } - - // Configure the SPHINX_INTERNAL__ALLOW_LOCAL_NODES env variable which allows local nodes - process.env['SPHINX_INTERNAL__ALLOW_LOCAL_NODES'] = 'true' - ;({ context } = makeMockSphinxContextForIntegrationTests([ - `contracts/test/script/issues/Owned.sol:Owned`, - ])) - - // Mock isLiveNetwork with a function that always returns false. - // In the rest of the test suite, we mock isLiveNetwork with a function that is always true which - // is why it's possible for us to run our integration tests against local nodes. - // We don't import and use the real isLiveNetwork function here because it's not necessary for this - // test and we don't want to accidentally use it in other parts of the test suite. - context.isLiveNetwork = async () => { - return false - } - }) - - afterEach(() => { - process.env = originalEnv - }) - - it('CHU-676: Deploys with call to safeAddress() in script', async () => { - const CHU676Path = './contracts/test/script/issues/CHU676.s.sol' - const isTestnet = true - - const { - proposalRequest, - networkConfigArray, - configArtifacts, - merkleTree, - } = await propose({ - confirm: false, // Show preview - networks: ['sepolia'], - isDryRun: true, - silent: true, - scriptPath: CHU676Path, - sphinxContext: context, - targetContract: 'CHU676', - }) - - if ( - !networkConfigArray || - !proposalRequest || - !configArtifacts || - !merkleTree - ) { - throw new Error(`Expected field(s) to be defined`) - } - - const sphinxModuleAddress = await getSphinxModuleAddressFromScript( - CHU676Path, - sepoliaRpcUrl, - 'CHU676' - ) - - const expectedContractAddress = ethers.getCreate2Address( - DETERMINISTIC_DEPLOYMENT_PROXY_ADDRESS, - ethers.ZeroHash, - ethers.keccak256( - ethers.concat([ - Owned.bytecode.object, - coder.encode(['address'], [sphinxModuleAddress]), - ]) - ) - ) - - assertValidProposalRequest( - proposalRequest, - 'Simple_Project', - isTestnet, - [11155111], - [ - { - networkTags: ['sepolia'], - executing: [ - { - referenceName: 'GnosisSafe', - functionName: 'deploy', - variables: {}, - address: proposalRequest.safeAddress, - value: '0', - }, - { - referenceName: 'SphinxModule', - functionName: 'deploy', - variables: {}, - address: proposalRequest.moduleAddress, - value: '0', - }, - { - referenceName: 'Owned', - functionName: 'deploy', - variables: { - _owner: sphinxModuleAddress, - }, - address: expectedContractAddress, - value: '0', - }, - ], - skipping: [], - chainId: '11155111', - safeAddress: proposalRequest.safeAddress, - }, - ], - networkConfigArray - ) - }) - }) - }) }) -/** - * Validates the `gasEstimates` array in the ProposalRequest. This mainly checks that the - * estimated gas is 30% greater than the actual gas used in the deployment. - */ -const assertValidGasEstimates = async ( - networkGasEstimates: ProposalRequest['gasEstimates'], - networkConfigArray: Array, - networks: Array, - scriptPath: string, - targetContract: string | undefined, - context: SphinxContext, - sig?: Array -) => { - // Check that the number of gas estimates matches the number of NetworkConfig objects with at least - // one action. - expect(networkGasEstimates.length).equals( - networkConfigArray.filter( - (networkConfig) => networkConfig.actionInputs.length > 0 - ).length - ) - - // Iterate over each network - for (let i = 0; i < networkGasEstimates.length; i++) { - const { chainId, estimatedGas } = networkGasEstimates[i] - const network = networks[i] - const networkConfig = networkConfigArray.find( - (config) => config.chainId === chainId.toString() - ) - - if (!networkConfig) { - throw new Error( - `Could not find the NetworkConfig for the current network.` - ) - } - - // Change the SphinxContext's `isLiveNetwork` method to be the standard function, which is - // necessary to accurately estimate the gas in the deployment below. Using the mocked function - // causes the deployment to use the `LiveNetworkCLI` execution mode because the mocked function - // always returns `true`. This causes an inaccurate gas estimate because the `LiveNetworkCLI` - // mode doesn't route transactions through the Managed Service contract. - context.isLiveNetwork = isLiveNetwork - - const { receipts } = await deploy({ - scriptPath, - network, - skipPreview: false, - silent: true, - sphinxContext: context, - verify: false, - targetContract, - sig, - }) - - if (!receipts) { - throw new Error('deployment failed for an unexpected reason') - } - - // We don't compare the number of actions in the NetworkConfig to the number of receipts in the - // user's deployment because multiple actions may be batched into a single call to the Sphinx - // Module's `execute` function. - - // Calculate the amount of gas used in the transaction receipts. - const actualGasUsed = receipts - .map((receipt) => receipt.gasUsed) - .map(Number) - // Sum the gas values - .reduce((a, b) => a + b, 0) - - const expectedGas = Math.round(actualGasUsed * 1.3) - const lowerBound = expectedGas * 0.99 - const upperBound = expectedGas * 1.01 - expect(Number(estimatedGas)).to.be.at.least(lowerBound) - expect(Number(estimatedGas)).to.be.at.most(upperBound) - } -} - const assertValidProposalRequest = ( proposalRequest: ProposalRequest, projectName: string, isTestnet: boolean, chainIds: Array, - previewNetworks: SphinxPreview['networks'], - networkConfigArray: Array + previewNetworks: SphinxPreview['networks'] ) => { expect(proposalRequest.apiKey).to.equal(sphinxApiKey) expect(proposalRequest.orgId).to.equal('test-org-id') expect(proposalRequest.isTestnet).to.equal(isTestnet) expect(proposalRequest.chainIds).to.deep.equal(chainIds) expect(proposalRequest.diff.networks).to.deep.equal(previewNetworks) - - // Expect funding estimate size to exactly match funding request listed in the deployment config - for (const estimate of proposalRequest.gasEstimates) { - const networkConfig = networkConfigArray.find( - (config) => config.chainId === estimate.chainId.toString() - ) - expect(networkConfig).not.undefined - expect(networkConfig?.safeFundingRequest?.fundsRequested).to.eq( - estimate.fundsRequested - ) - } } diff --git a/packages/plugins/test/mocha/foundry/utils.spec.ts b/packages/plugins/test/mocha/foundry/utils.spec.ts index 8dd6fb4b2..ee0245a7c 100644 --- a/packages/plugins/test/mocha/foundry/utils.spec.ts +++ b/packages/plugins/test/mocha/foundry/utils.spec.ts @@ -933,7 +933,6 @@ describe('Utils', async () => { const unsupportedNetworkOne = 'unsupported1' const unsupportedNetworkTwo = 'unsupported2' - let isLiveNetwork: sinon.SinonSpy let rpcEndpoints: FoundryToml['rpcEndpoints'] let getNetworkStub: sinon.SinonStub @@ -948,7 +947,6 @@ describe('Utils', async () => { getNetworkStub = sinon.stub() - isLiveNetwork = sinon.fake.resolves(true) sinon .stub(SphinxJsonRpcProvider.prototype, 'getNetwork') .callsFake(getNetworkStub) @@ -960,7 +958,7 @@ describe('Utils', async () => { it('throws an error if no CLI networks are provided', async () => { await expect( - validateProposalNetworks([], [], [], rpcEndpoints, isLiveNetwork) + validateProposalNetworks([], [], [], rpcEndpoints) ).to.be.rejectedWith( `Expected at least one network, but none were supplied.` ) @@ -969,26 +967,14 @@ describe('Utils', async () => { it('throws an error for missing RPC endpoints', async () => { const unknownNetworks = ['unknown1', 'unknown2'] await expect( - validateProposalNetworks( - unknownNetworks, - [], - [], - rpcEndpoints, - isLiveNetwork - ) + validateProposalNetworks(unknownNetworks, [], [], rpcEndpoints) ).to.be.rejectedWith(getMissingEndpointErrorMessage(unknownNetworks)) }) it('throws an error for failed requests to RPC endpoints', async () => { getNetworkStub.rejects(new Error('Request failed')) await expect( - validateProposalNetworks( - validNetworks, - [], - [], - rpcEndpoints, - isLiveNetwork - ) + validateProposalNetworks(validNetworks, [], [], rpcEndpoints) ).to.be.rejectedWith(getFailedRequestErrorMessage(validNetworks)) }) @@ -1012,8 +998,7 @@ describe('Utils', async () => { [unsupportedNetworkOne, unsupportedNetworkTwo], [], [], - rpcEndpoints, - isLiveNetwork + rpcEndpoints ) ).to.be.rejectedWith( getUnsupportedNetworkErrorMessage(unsupportedNetworks) @@ -1024,13 +1009,7 @@ describe('Utils', async () => { getNetworkStub.resolves({ chainId: BigInt(1) }) isLiveNetwork = sinon.fake.resolves(false) await expect( - validateProposalNetworks( - validNetworks, - [], - [], - rpcEndpoints, - isLiveNetwork - ) + validateProposalNetworks(validNetworks, [], [], rpcEndpoints) ).to.be.rejectedWith(getLocalNetworkErrorMessage(validNetworks)) }) @@ -1046,13 +1025,7 @@ describe('Utils', async () => { getNetworkStub.onThirdCall().resolves({ chainId: BigInt(11155111) }) // Test network (Sepolia) await expect( - validateProposalNetworks( - validNetworks, - [], - [], - rpcEndpoints, - isLiveNetwork - ) + validateProposalNetworks(validNetworks, [], [], rpcEndpoints) ).to.be.rejectedWith(getMixedNetworkTypeErrorMessage(mixedNetworks)) }) @@ -1064,8 +1037,7 @@ describe('Utils', async () => { ['mainnets'], [], [validTestnetOne], - rpcEndpoints, - isLiveNetwork + rpcEndpoints ) ).to.be.rejectedWith(SphinxConfigMainnetsContainsTestnetsErrorMessage) }) @@ -1078,8 +1050,7 @@ describe('Utils', async () => { ['testnets'], [validMainnetOne, validMainnetTwo], [], - rpcEndpoints, - isLiveNetwork + rpcEndpoints ) ).to.be.rejectedWith(SphinxConfigTestnetsContainsMainnetsErrorMessage) }) @@ -1092,8 +1063,7 @@ describe('Utils', async () => { [validMainnetOne, validMainnetTwo], [], [], - rpcEndpoints, - isLiveNetwork + rpcEndpoints ) expect(result.rpcUrls).to.deep.equals([ rpcEndpoints[validMainnetOne], @@ -1110,8 +1080,7 @@ describe('Utils', async () => { ['mainnets'], [], [validMainnetOne, validMainnetTwo], - rpcEndpoints, - isLiveNetwork + rpcEndpoints ) expect(result.rpcUrls).to.deep.equals([ rpcEndpoints[validMainnetOne], @@ -1127,8 +1096,7 @@ describe('Utils', async () => { ['testnets'], [validTestnetOne], [], - rpcEndpoints, - isLiveNetwork + rpcEndpoints ) expect(result.rpcUrls).to.deep.equals([rpcEndpoints[validTestnetOne]]) expect(result.isTestnet).to.be.true