diff --git a/contracts/foundry.toml b/contracts/foundry.toml index ff952ebb..d3d81d7d 100644 --- a/contracts/foundry.toml +++ b/contracts/foundry.toml @@ -1,8 +1,9 @@ [profile.default] -solc = '0.8.16' +solc = '0.8.25' src = "src" out = "out" libs = ["lib"] +evm_version = "cancun" optimizer = true optimizer_runs = 100_000 fs_permissions = [{ access = "read-write", path = "./"}] diff --git a/contracts/src/NetworkAdmin.sol b/contracts/src/NetworkAdmin.sol new file mode 100644 index 00000000..84adfbab --- /dev/null +++ b/contracts/src/NetworkAdmin.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {NetworkStorage} from "./NetworkStorage.sol"; +import {Initializable} from "@openzeppelin-upgradeable/contracts/proxy/utils/Initializable.sol"; +import {OwnableUpgradeable} from "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol"; + +interface INetworkAdminEvents { + event FeeVaultSet(address oldFeeVault, address newFeeVault); + event DefaultProverUpdated(address indexed prover, bool added); +} + +interface INetworkAdminErrors { + error RecoverFailed(); +} + +interface INetworkAdmin is INetworkAdminEvents, INetworkAdminErrors { + function addDefaultProver(address prover) external; + function removeDefaultProver(address prover) external; + function setFeeVault(address feeVault) external; + function recover(address to, uint256 amount) external; +} + +abstract contract NetworkAdmin is + INetworkAdmin, + NetworkStorage, + Initializable, + OwnableUpgradeable +{ + function __NetworkAdmin_init(address _owner, address _feeVault, address _defaultProver) + internal + { + _transferOwnership(_owner); + feeVault = _feeVault; + allowedProvers[address(0)][_defaultProver] = true; + } + + function addDefaultProver(address _prover) external onlyOwner { + allowedProvers[address(0)][_prover] = true; + emit DefaultProverUpdated(_prover, true); + } + + function removeDefaultProver(address _prover) external onlyOwner { + delete allowedProvers[address(0)][_prover]; + emit DefaultProverUpdated(_prover, false); + } + + function setFeeVault(address _feeVault) external onlyOwner { + emit FeeVaultSet(feeVault, _feeVault); + feeVault = _feeVault; + } + + function recover(address _to, uint256 _amount) external onlyOwner { + (bool success,) = _to.call{value: _amount}(""); + if (!success) { + revert RecoverFailed(); + } + } +} diff --git a/contracts/src/NetworkGateway.sol b/contracts/src/NetworkGateway.sol new file mode 100644 index 00000000..d85f9584 --- /dev/null +++ b/contracts/src/NetworkGateway.sol @@ -0,0 +1,326 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {IFeeVault} from "./payments/interfaces/IFeeVault.sol"; +import {INetworkVerifier} from "./interfaces/INetworkVerifier.sol"; +import {NetworkStorage} from "./NetworkStorage.sol"; +import {NetworkAdmin} from "./NetworkAdmin.sol"; +import {NetworkRegistry} from "./NetworkRegistry.sol"; + +interface INetworkGatewayEvents { + event RequestCallback( + uint32 indexed nonce, + address indexed verifier, + bytes32 programHash, + bytes input, + bytes context, + address callback, + bytes4 callbackSelector, + uint32 callbackGasLimit, + uint256 value + ); + event RequestCall( + address indexed verifier, + bytes32 programHash, + bytes input, + address callback, + bytes callbackCalldata, + uint32 callbackGasLimit, + address caller, + uint256 value + ); + event FulfilledCallback( + uint32 indexed nonce, + address indexed verifier, + bytes32 programHash, + address callback, + address prover, + bytes32 inputHash, + bytes32 outputHash + ); + event FullfilledCall( + address indexed verifier, + bytes32 programHash, + address callback, + address prover, + bytes32 inputHash, + bytes32 outputHash + ); +} + +interface INetworkGatewayErrors { + error ReentrantFulfill(); + error InvalidRequest(uint32 nonce, bytes32 expected, bytes32 actual); + error InvalidProof(); + error CallbackFailed(bytes4 callbackSelector, bytes output, bytes context); + error CallFailed(address callback, bytes callbackData); + error InvalidCall(address verifier, bytes input); +} + +interface INetworkGateway is INetworkGatewayEvents, INetworkGatewayErrors { + function requestCallback( + address verifier, + bytes32 programHash, + bytes calldata input, + bytes calldata context, + bytes4 callbackSelector, + uint32 callbackGasLimit + ) external payable returns (bytes32); + + function requestCall( + address verifier, + bytes32 programHash, + bytes calldata input, + address callback, + bytes calldata callbackCalldata, + uint32 callbackGasLimit + ) external payable; + + function isCallback() external view returns (bool); + + function verifiedCall(address verifier, bytes calldata input) + external + view + returns (bytes memory); + + function fulfillCallback( + uint32 nonce, + address verifier, + bytes32 programHash, + bytes32 inputHash, + address callback, + bytes4 callbackSelector, + uint32 callbackGasLimit, + bytes calldata context, + bytes calldata output, + bytes calldata proof + ) external; + + function fulfillCall( + address verifier, + bytes calldata input, + bytes calldata output, + bytes calldata proof, + address callback, + bytes calldata callbackData + ) external; +} + +contract NetworkGateway is INetworkGateway, NetworkStorage, NetworkAdmin, NetworkRegistry { + modifier nonReentrant() { + if ( + executingCallback || verifiedVerifier != address(0) || verifiedInputHash != bytes32(0) + || verifiedOutput.length != 0 + ) { + revert ReentrantFulfill(); + } + _; + } + + function initialize(address _owner, address _feeVault, address _defaultProver) + external + initializer + { + _transferOwnership(_owner); + feeVault = _feeVault; + allowedProvers[address(0)][_defaultProver] = true; + } + + function requestCallback( + address _verifier, + bytes32 _programHash, + bytes calldata _input, + bytes calldata _context, + bytes4 _callbackSelector, + uint32 _callbackGasLimit + ) external payable override returns (bytes32) { + bytes32 inputHash = sha256(_input); + bytes32 contextHash = keccak256(_context); + address callback = msg.sender; + bytes32 requestHash = _requestHash( + nonce, + _verifier, + _programHash, + inputHash, + contextHash, + callback, + _callbackSelector, + _callbackGasLimit + ); + + requests[nonce] = requestHash; + emit RequestCallback( + nonce, + _verifier, + _programHash, + _input, + _context, + callback, + _callbackSelector, + _callbackGasLimit, + msg.value + ); + + nonce++; + + if (msg.value > 0 && feeVault != address(0)) { + IFeeVault(feeVault).depositNative{value: msg.value}(callback); + } + + return requestHash; + } + + function requestCall( + address _verifier, + bytes32 _programHash, + bytes calldata _input, + address _callback, + bytes calldata _callbackCalldata, + uint32 _callbackGasLimit + ) external payable override { + emit RequestCall( + _verifier, + _programHash, + _input, + _callback, + _callbackCalldata, + _callbackGasLimit, + msg.sender, + msg.value + ); + + if (msg.value > 0 && feeVault != address(0)) { + IFeeVault(feeVault).depositNative{value: msg.value}(msg.sender); + } + } + + function isCallback() external view override returns (bool) { + return executingCallback; + } + + function verifiedCall(address _verifier, bytes calldata _input) + external + view + override + returns (bytes memory) + { + bytes32 inputHash = sha256(_input); + if (verifiedVerifier == _verifier && verifiedInputHash == inputHash) { + return verifiedOutput; + } else { + revert InvalidCall(_verifier, _input); + } + } + + function fulfillCallback( + uint32 _nonce, + address _verifier, + bytes32 _programHash, + bytes32 _inputHash, + address _callback, + bytes4 _callbackSelector, + uint32 _callbackGasLimit, + bytes calldata _context, + bytes calldata _output, + bytes calldata _proof + ) external nonReentrant onlyProver(_verifier) { + bytes32 contextHash = keccak256(_context); + bytes32 requestHash = _requestHash( + _nonce, + _verifier, + _programHash, + _inputHash, + contextHash, + _callback, + _callbackSelector, + _callbackGasLimit + ); + + if (requests[_nonce] != requestHash) { + revert InvalidRequest(_nonce, requests[_nonce], requestHash); + } + delete requests[_nonce]; + + bytes32 outputHash = sha256(_output); + + _verify(_verifier, _programHash, _inputHash, outputHash, _proof); + + executingCallback = true; + (bool status,) = _callback.call{gas: _callbackGasLimit}( + abi.encodeWithSelector(_callbackSelector, _output, _context) + ); + delete executingCallback; + + if (!status) { + revert CallbackFailed(_callbackSelector, _output, _context); + } + + emit FulfilledCallback( + _nonce, _verifier, _programHash, _callback, msg.sender, _inputHash, outputHash + ); + } + + function fulfillCall( + address _verifier, + bytes32 _programHash, + bytes calldata _input, + bytes calldata _output, + bytes calldata _proof, + address _callback, + bytes calldata _callbackData + ) external nonReentrant onlyProver(_verifier) { + bytes32 inputHash = sha256(_input); + bytes32 outputHash = sha256(_output); + + _verify(_verifier, _programHash, inputHash, outputHash, _proof); + + verifiedVerifier = _verifier; + verifiedInputHash = inputHash; + verifiedOutput = _output; + (bool status,) = _callback.call(_callbackData); + if (!status) { + revert CallFailed(_callback, _callbackData); + } + delete verifiedVerifier; + delete verifiedInputHash; + delete verifiedOutput; + + emit FullfilledCall(_verifier, _programHash, _callback, msg.sender, inputHash, outputHash); + } + + function _requestHash( + uint32 _nonce, + address _verifier, + bytes32 _programHash, + bytes32 _inputHash, + bytes32 _contextHash, + address _callback, + bytes4 _callbackSelector, + uint32 _callbackGasLimit + ) internal pure returns (bytes32) { + return keccak256( + abi.encodePacked( + _nonce, + _verifier, + _programHash, + _inputHash, + _contextHash, + _callback, + _callbackSelector, + _callbackGasLimit + ) + ); + } + + function _verify( + address _verifier, + bytes32 _programHash, + bytes32 _inputHash, + bytes32 _outputHash, + bytes calldata _proof + ) internal { + if (!INetworkVerifier(_verifier).verify(_programHash, _inputHash, _outputHash, _proof)) { + revert InvalidProof(); + } + } +} diff --git a/contracts/src/NetworkRegistry.sol b/contracts/src/NetworkRegistry.sol new file mode 100644 index 00000000..d697b133 --- /dev/null +++ b/contracts/src/NetworkRegistry.sol @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {WhitelistStatus} from "./interfaces/ISuccinctGateway.sol"; +import {NetworkStorage} from "./NetworkStorage.sol"; + +interface INetworkRegistryEvents { + event VerifierRegistered(address indexed verifier, address owner); + event CustomProverUpdated(address indexed verifier, address indexed prover, bool added); + event WhitelistStatusUpdated(address indexed verifier, WhitelistStatus status); +} + +interface INetworkRegistryErrors { + error OnlyProver(address verifier, address prover); + error BytecodeCannotBeEmpty(); + error VerifierCannotBeZero(); + error VerifierAlreadyExists(address verifier); + error NotVerifierOwner(address expectedOwner, address actualOwner); +} + +interface INetworkRegistry is INetworkRegistryEvents, INetworkRegistryErrors { + function registerVerifier(address owner, address verifier) external; + function deployAndRegisterVerifier(address owner, bytes memory bytecode, bytes32 salt) + external + returns (address verifier); + function setWhitelistStatus(address verifier, WhitelistStatus status) external; + function addCustomProver(address verifier, address prover) external; + function removeCustomProver(address verifier, address prover) external; +} + +abstract contract NetworkRegistry is INetworkRegistry, NetworkStorage { + modifier onlyProver(address _verifier) { + if ( + whitelistStatus[_verifier] == WhitelistStatus.Default + && !allowedProvers[address(0)][msg.sender] + ) { + revert OnlyProver(_verifier, msg.sender); + } else if ( + whitelistStatus[_verifier] == WhitelistStatus.Custom + && !allowedProvers[_verifier][msg.sender] + ) { + revert OnlyProver(_verifier, msg.sender); + } + _; + } + + function registerVerifier(address _owner, address _verifier) external override { + _register(_owner, _verifier); + } + + function deployAndRegisterVerifier(address _owner, bytes calldata _bytecode, bytes32 _salt) + external + override + returns (address verifier) + { + verifier = _deploy(_bytecode, _salt); + _register(_owner, verifier); + } + + function setWhitelistStatus(address _verifier, WhitelistStatus _status) external override { + if (msg.sender != verifierOwners[_verifier]) { + revert NotVerifierOwner(msg.sender, verifierOwners[_verifier]); + } + whitelistStatus[_verifier] = _status; + emit WhitelistStatusUpdated(_verifier, _status); + } + + function addCustomProver(address _verifier, address _prover) external override { + if (msg.sender != verifierOwners[_verifier]) { + revert NotVerifierOwner(msg.sender, verifierOwners[_verifier]); + } + allowedProvers[_verifier][_prover] = true; + emit CustomProverUpdated(_verifier, _prover, true); + } + + function removeCustomProver(address _verifier, address _prover) external override { + if (msg.sender != verifierOwners[_verifier]) { + revert NotVerifierOwner(msg.sender, verifierOwners[_verifier]); + } + delete allowedProvers[_verifier][_prover]; + emit CustomProverUpdated(_verifier, _prover, false); + } + + function _deploy(bytes memory _bytecode, bytes32 _salt) + internal + returns (address deployedAddr) + { + if (_bytecode.length == 0) revert BytecodeCannotBeEmpty(); + + assembly { + deployedAddr := create2(0, add(_bytecode, 32), mload(_bytecode), _salt) + } + } + + function _register(address _owner, address _verifier) internal { + if (_verifier == address(0)) { + revert VerifierCannotBeZero(); + } + if (verifierOwners[_verifier] != address(0)) { + revert VerifierAlreadyExists(_verifier); + } + verifierOwners[_verifier] = _owner; + + emit VerifierRegistered(_verifier, _owner); + } +} diff --git a/contracts/src/NetworkStorage.sol b/contracts/src/NetworkStorage.sol new file mode 100644 index 00000000..5d2dba98 --- /dev/null +++ b/contracts/src/NetworkStorage.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {WhitelistStatus} from "./interfaces/ISuccinctGateway.sol"; + +abstract contract NetworkStorage { + address public feeVault; + + uint32 public nonce; + + bool internal executingCallback; + + address internal verifiedVerifier; + + bytes32 internal verifiedInputHash; + + bytes internal verifiedOutput; + + mapping(uint32 => bytes32) public requests; + + mapping(address => address) public verifierOwners; + + mapping(address => WhitelistStatus) public whitelistStatus; + + mapping(address => mapping(address => bool)) public allowedProvers; +} diff --git a/contracts/src/interfaces/INetworkVerifier.sol b/contracts/src/interfaces/INetworkVerifier.sol new file mode 100644 index 00000000..646b4fcf --- /dev/null +++ b/contracts/src/interfaces/INetworkVerifier.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.5.0; + +interface INetworkVerifier { + function verify( + bytes32 programHash, + bytes32 inputHash, + bytes32 outputHash, + bytes calldata proof + ) external returns (bool); + + function verificationKeyHash() external view returns (bytes32); +}