diff --git a/.env.example b/.env.example index b28c8533..30bc151d 100644 --- a/.env.example +++ b/.env.example @@ -2,3 +2,5 @@ export MNEMONIC="YOUR_MNEMONIC" # Private key with the permission to use the network prover SP1_PRIVATE_KEY="PRIVATE_KEY" +# SP1_PROVER={network|local|mock} +SP1_PROVER=network diff --git a/e2e/interchaintestv8/ibc_eureka_test.go b/e2e/interchaintestv8/ibc_eureka_test.go index e70b8369..4d0f7981 100644 --- a/e2e/interchaintestv8/ibc_eureka_test.go +++ b/e2e/interchaintestv8/ibc_eureka_test.go @@ -73,6 +73,7 @@ func (s *IbcEurekaTestSuite) SetupSuite(ctx context.Context) { eth, simd := s.ChainA, s.ChainB + var prover string s.Require().True(s.Run("Set up environment", func() { err := os.Chdir("../..") s.Require().NoError(err) @@ -92,9 +93,20 @@ func (s *IbcEurekaTestSuite) SetupSuite(ctx context.Context) { s.faucet, err = crypto.HexToECDSA(testvalues.FaucetPrivateKey) s.Require().NoError(err) + prover = os.Getenv(testvalues.EnvKeySp1Prover) + switch prover { + case "": + prover = testvalues.EnvValueSp1Prover_Network + case testvalues.EnvValueSp1Prover_Mock: + s.T().Logf("Using mock prover") + case testvalues.EnvValueSp1Prover_Network: + default: + s.Require().Fail("invalid prover type: %s", prover) + } + os.Setenv(testvalues.EnvKeyEthRPC, eth.GetHostRPCAddress()) os.Setenv(testvalues.EnvKeyTendermintRPC, simd.GetHostRPCAddress()) - os.Setenv(testvalues.EnvKeySp1Prover, "network") + os.Setenv(testvalues.EnvKeySp1Prover, prover) os.Setenv(testvalues.EnvKeyOperatorPrivateKey, hex.EncodeToString(crypto.FromECDSA(operatorKey))) // make sure that the SP1_PRIVATE_KEY is set. s.Require().NotEmpty(os.Getenv(testvalues.EnvKeySp1PrivateKey)) @@ -122,15 +134,35 @@ func (s *IbcEurekaTestSuite) SetupSuite(ctx context.Context) { "-o", "e2e/artifacts/genesis.json", )) - stdout, stderr, err := eth.ForgeScript(ctx, s.deployer.KeyName(), ethereum.ForgeScriptOpts{ - ContractRootDir: ".", - SolidityContract: "script/E2ETestDeploy.s.sol", - RawOptions: []string{ - "--json", - "--sender", s.deployer.FormattedAddress(), // This, combined with the keyname, makes msg.sender the deployer - }, - }) - s.Require().NoError(err, fmt.Sprintf("error deploying contracts: \nstderr: %s\nstdout: %s", stderr, stdout)) + var ( + stdout []byte + stderr []byte + err error + ) + switch prover { + case testvalues.EnvValueSp1Prover_Mock: + stdout, stderr, err = eth.ForgeScript(ctx, s.deployer.KeyName(), ethereum.ForgeScriptOpts{ + ContractRootDir: ".", + SolidityContract: "script/MockE2ETestDeploy.s.sol", + RawOptions: []string{ + "--json", + "--sender", s.deployer.FormattedAddress(), // This, combined with the keyname, makes msg.sender the deployer + }, + }) + s.Require().NoError(err, fmt.Sprintf("error deploying contracts: \nstderr: %s\nstdout: %s", stderr, stdout)) + case testvalues.EnvValueSp1Prover_Network: + stdout, stderr, err = eth.ForgeScript(ctx, s.deployer.KeyName(), ethereum.ForgeScriptOpts{ + ContractRootDir: ".", + SolidityContract: "script/E2ETestDeploy.s.sol", + RawOptions: []string{ + "--json", + "--sender", s.deployer.FormattedAddress(), // This, combined with the keyname, makes msg.sender the deployer + }, + }) + s.Require().NoError(err, fmt.Sprintf("error deploying contracts: \nstderr: %s\nstdout: %s", stderr, stdout)) + default: + s.Require().Fail("invalid prover type: %s", prover) + } client, err := ethclient.Dial(eth.GetHostRPCAddress()) s.Require().NoError(err) diff --git a/e2e/interchaintestv8/testvalues/values.go b/e2e/interchaintestv8/testvalues/values.go index 0f4b6e8d..7788988c 100644 --- a/e2e/interchaintestv8/testvalues/values.go +++ b/e2e/interchaintestv8/testvalues/values.go @@ -28,6 +28,11 @@ const ( // EnvKeySp1PrivateKey Private key for the prover network. EnvKeySp1PrivateKey = "SP1_PRIVATE_KEY" + // EnvValueSp1Prover_Network is the prover type for the network prover. + EnvValueSp1Prover_Network = "network" + // EnvValueSp1Prover_Mock is the prover type for the mock prover. + EnvValueSp1Prover_Mock = "mock" + // FaucetPrivateKey is the private key of the faucet account. // '0x' prefix is trimmed. FaucetPrivateKey = "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" diff --git a/script/E2ETestDeploy.s.sol b/script/E2ETestDeploy.s.sol index 83b42165..f2bdba16 100644 --- a/script/E2ETestDeploy.s.sol +++ b/script/E2ETestDeploy.s.sol @@ -1,6 +1,10 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity >=0.8.25 <0.9.0; +/* + This script is used for end-to-end testing with SP1_PROVER=network. +*/ + import { stdJson } from "forge-std/StdJson.sol"; import { Script } from "forge-std/Script.sol"; import { SP1ICS07Tendermint } from "@cosmos/sp1-ics07-tendermint/SP1ICS07Tendermint.sol"; diff --git a/script/MockE2ETestDeploy.s.sol b/script/MockE2ETestDeploy.s.sol new file mode 100644 index 000000000..bf5c186d --- /dev/null +++ b/script/MockE2ETestDeploy.s.sol @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.25 <0.9.0; + +/* + This script is used for local testing with SP1_PROVER=mock. +*/ + +import { stdJson } from "forge-std/StdJson.sol"; +import { Script } from "forge-std/Script.sol"; +import { SP1ICS07Tendermint } from "@cosmos/sp1-ics07-tendermint/SP1ICS07Tendermint.sol"; +import { IICS07TendermintMsgs } from "@cosmos/sp1-ics07-tendermint/msgs/IICS07TendermintMsgs.sol"; +import { ICS02Client } from "../src/ICS02Client.sol"; +import { ICS26Router } from "../src/ICS26Router.sol"; +import { ICS20Transfer } from "../src/ICS20Transfer.sol"; +import { TestERC20 } from "../test/TestERC20.sol"; +import { AcceptAllSP1Verifier } from "../test/AcceptAllSP1Verifier.sol"; +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { ICS20Lib } from "../src/utils/ICS20Lib.sol"; + +struct SP1ICS07TendermintGenesisJson { + bytes trustedClientState; + bytes trustedConsensusState; + bytes32 updateClientVkey; + bytes32 membershipVkey; + bytes32 ucAndMembershipVkey; +} + +/// @dev See the Solidity Scripting tutorial: https://book.getfoundry.sh/tutorials/solidity-scripting +contract MockE2ETestDeploy is Script { + using stdJson for string; + + string public constant E2E_FAUCET = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"; + + function run() public returns (string memory) { + // Read the initialization parameters for the SP1 Tendermint contract. + SP1ICS07TendermintGenesisJson memory genesis = loadGenesis("genesis.json"); + IICS07TendermintMsgs.ConsensusState memory trustedConsensusState = + abi.decode(genesis.trustedConsensusState, (IICS07TendermintMsgs.ConsensusState)); + bytes32 trustedConsensusHash = keccak256(abi.encode(trustedConsensusState)); + + vm.startBroadcast(); + address deployerAddress = msg.sender; // This is being set in the e2e test + + // Deploy the SP1 ICS07 Tendermint light client + AcceptAllSP1Verifier verifier = new AcceptAllSP1Verifier(); + SP1ICS07Tendermint ics07Tendermint = new SP1ICS07Tendermint( + genesis.updateClientVkey, + genesis.membershipVkey, + genesis.ucAndMembershipVkey, + address(verifier), + genesis.trustedClientState, + trustedConsensusHash + ); + + // Deploy IBC Eureka + ICS02Client ics02Client = new ICS02Client(deployerAddress); + ICS26Router ics26Router = new ICS26Router(address(ics02Client), deployerAddress); + ICS20Transfer ics20Transfer = new ICS20Transfer(address(ics26Router)); + TestERC20 erc20 = new TestERC20(); + + // Wire Transfer app + ics26Router.addIBCApp("transfer", address(ics20Transfer)); + + // Mint some tokens + (address addr, bool ok) = ICS20Lib.hexStringToAddress(E2E_FAUCET); + require(ok, "invalid address"); + erc20.mint(addr, 100_000_000_000); + + vm.stopBroadcast(); + + string memory json = "json"; + json.serialize("ics07Tendermint", Strings.toHexString(address(ics07Tendermint))); + json.serialize("ics02Client", Strings.toHexString(address(ics02Client))); + json.serialize("ics26Router", Strings.toHexString(address(ics26Router))); + json.serialize("ics20Transfer", Strings.toHexString(address(ics20Transfer))); + string memory finalJson = json.serialize("erc20", Strings.toHexString(address(erc20))); + + return finalJson; + } + + function loadGenesis(string memory fileName) public view returns (SP1ICS07TendermintGenesisJson memory) { + string memory root = vm.projectRoot(); + string memory path = string.concat(root, "/e2e/artifacts/", fileName); + string memory json = vm.readFile(path); + bytes memory trustedClientState = json.readBytes(".trustedClientState"); + bytes memory trustedConsensusState = json.readBytes(".trustedConsensusState"); + bytes32 updateClientVkey = json.readBytes32(".updateClientVkey"); + bytes32 membershipVkey = json.readBytes32(".membershipVkey"); + bytes32 ucAndMembershipVkey = json.readBytes32(".ucAndMembershipVkey"); + + SP1ICS07TendermintGenesisJson memory fixture = SP1ICS07TendermintGenesisJson({ + trustedClientState: trustedClientState, + trustedConsensusState: trustedConsensusState, + updateClientVkey: updateClientVkey, + membershipVkey: membershipVkey, + ucAndMembershipVkey: ucAndMembershipVkey + }); + + return fixture; + } +} diff --git a/test/AcceptAllSP1Verifier.sol b/test/AcceptAllSP1Verifier.sol new file mode 100644 index 000000000..56117372 --- /dev/null +++ b/test/AcceptAllSP1Verifier.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.25 <0.9.0; + +// solhint-disable no-empty-blocks + +import { ISP1Verifier } from "@sp1-contracts/ISP1Verifier.sol"; + +/// @dev This SP1 verifier accepts all proofs, for testing purposes. +/// @dev It is required due to the issues we are running into with '@sp1-contracts/SP1MockVerifier.sol'. +/// @dev This contract can be removed once the issues are resolved. +contract AcceptAllSP1Verifier is ISP1Verifier { + function verifyProof(bytes32, bytes calldata, bytes calldata) external view override { + // Accept all proofs + } +}