Skip to content

Commit

Permalink
Raunak/upgradeable dispatcher (#56)
Browse files Browse the repository at this point in the history
* outsource pure functions from dispatcher to ibc

* rename consensus manager -> light client

* change ibc contract to be a library to cut down on dispatcher contract size

* add Dispatcher interface

* forge install: openzeppelin-contracts-upgradeable

v4.9.6

* implement proxy design

* get all existing test cases working w/ proxy

* add access control tests

* add dispatcher unit tests for upgrade

* format + lint, update dispatcher interface

* ignore libraries for formatting

* respond to code rabbit comments'

* rename dispatcher, impl -> dispatcherProxy, dispatcherImplementation

* forge fmt

* get current test suite working

* test state in upgrade tests

* fmt + lint

* rename test utils file

* rename test utils file

* rename testUtils -> testUtilsTest

* change test library to abstract contract to avoid test contract size limiting

---------

Co-authored-by: Raunak Singh <[email protected]>
Co-authored-by: Raunak Singh <[email protected]>
  • Loading branch information
3 people authored Mar 21, 2024
1 parent cda477d commit 7029199
Show file tree
Hide file tree
Showing 23 changed files with 1,387 additions and 262 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@
[submodule "lib/base64"]
path = lib/base64
url = https://github.com/Brechtpd/base64.git
[submodule "lib/openzeppelin-contracts-upgradeable"]
path = lib/openzeppelin-contracts-upgradeable
url = https://github.com/openzeppelin/openzeppelin-contracts-upgradeable
128 changes: 76 additions & 52 deletions contracts/core/Dispatcher.sol

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
pragma solidity ^0.8.0;

import {L1Header, ProofVerifier, OpL2StateProof, Ics23Proof} from "../interfaces/ProofVerifier.sol";
import {ConsensusStateManager} from "../interfaces/ConsensusStateManager.sol";
import {LightClient} from "../interfaces/LightClient.sol";
import {L1Block} from "optimism/L2/L1Block.sol";

// OptimisticConsensusStateManager manages the appHash at different
// OptimisticLightClient manages the appHash at different
// heights and track the fraud proof end time for them.
contract OptimisticConsensusStateManager is ConsensusStateManager {
contract OptimisticLightClient is LightClient {
// consensusStates maps from the height to the appHash.
mapping(uint256 => uint256) public consensusStates;

Expand Down
111 changes: 111 additions & 0 deletions contracts/interfaces/IDispatcher.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IbcDispatcher, IbcEventsEmitter} from "./IbcDispatcher.sol";

import {L1Header, OpL2StateProof, Ics23Proof} from "./ProofVerifier.sol";
import {IbcChannelReceiver, IbcPacketReceiver} from "./IbcReceiver.sol";
import {
Channel,
CounterParty,
ChannelOrder,
IbcPacket,
ChannelState,
AckPacket,
IBCErrors,
IbcUtils,
Ibc
} from "../libs/Ibc.sol";

interface IDispatcher is IbcDispatcher, IbcEventsEmitter {
function setPortPrefix(string calldata _portPrefix) external;

function updateClientWithOptimisticConsensusState(
L1Header calldata l1header,
OpL2StateProof calldata proof,
uint256 height,
uint256 appHash
) external returns (uint256 fraudProofEndTime, bool ended);

/**
* This function is called by a 'relayer' on behalf of a dApp. The dApp should implement IbcChannelHandler's
* onChanOpenInit. If the callback succeeds, the dApp should return the selected version and the emitted event
* will be relayed to the IBC/VIBC hub chain.
*/
function channelOpenInit(
IbcChannelReceiver portAddress,
string calldata version,
ChannelOrder ordering,
bool feeEnabled,
string[] calldata connectionHops,
string calldata counterpartyPortId
) external;

/**
* This function is called by a 'relayer' on behalf of a dApp. The dApp should implement IbcChannelHandler's
* onChanOpenTry. If the callback succeeds, the dApp should return the selected version and the emitted event
* will be relayed to the IBC/VIBC hub chain.
*/
function channelOpenTry(
IbcChannelReceiver portAddress,
CounterParty calldata local,
ChannelOrder ordering,
bool feeEnabled,
string[] calldata connectionHops,
CounterParty calldata counterparty,
Ics23Proof calldata proof
) external;

/**
* This func is called by a 'relayer' after the IBC/VIBC hub chain has processed the ChannelOpenTry event.
* The dApp should implement the onChannelConnect method to handle the third channel handshake method: ChanOpenAck
*/
function channelOpenAck(
IbcChannelReceiver portAddress,
CounterParty calldata local,
string[] calldata connectionHops,
ChannelOrder ordering,
bool feeEnabled,
CounterParty calldata counterparty,
Ics23Proof calldata proof
) external;

/**
* This func is called by a 'relayer' after the IBC/VIBC hub chain has processed the ChannelOpenTry event.
* The dApp should implement the onChannelConnect method to handle the last channel handshake method:
* ChannelOpenConfirm
*/
function channelOpenConfirm(
IbcChannelReceiver portAddress,
CounterParty calldata local,
string[] calldata connectionHops,
ChannelOrder ordering,
bool feeEnabled,
CounterParty calldata counterparty,
Ics23Proof calldata proof
) external;

function closeIbcChannel(bytes32 channelId) external;

function sendPacket(bytes32 channelId, bytes calldata packet, uint64 timeoutTimestamp) external;

function acknowledgement(
IbcPacketReceiver receiver,
IbcPacket calldata packet,
bytes calldata ack,
Ics23Proof calldata proof
) external;

function timeout(IbcPacketReceiver receiver, IbcPacket calldata packet, Ics23Proof calldata proof) external;

function recvPacket(IbcPacketReceiver receiver, IbcPacket calldata packet, Ics23Proof calldata proof) external;

function getOptimisticConsensusState(uint256 height)
external
view
returns (uint256 appHash, uint256 fraudProofEndTime, bool ended);

function portIdAddressMatch(address addr, string calldata portId) external view returns (bool isMatch);

function getChannel(address portAddress, bytes32 channelId) external view returns (Channel memory channel);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pragma solidity ^0.8.0;

import {Ics23Proof, L1Header, OpL2StateProof} from "./ProofVerifier.sol";

interface ConsensusStateManager {
interface LightClient {
/**
* addOpConsensusState adds an appHash to internal store and
* returns the fraud proof end time, and a bool flag indicating if
Expand Down
48 changes: 48 additions & 0 deletions contracts/libs/Ibc.sol
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,54 @@ library IBCErrors {
library IbcUtils {
error StringTooLong();

/**
* Convert a non-0x-prefixed hex string to an address
* @param hexStr hex string to convert to address. Note that the hex string must not include a 0x prefix.
* hexStr is case-insensitive.
*/
function hexStrToAddress(string memory hexStr) public pure returns (address addr) {
if (bytes(hexStr).length != 40) {
revert IBCErrors.invalidHexStringLength();
}

bytes memory strBytes = bytes(hexStr);
bytes memory addrBytes = new bytes(20);

for (uint256 i = 0; i < 20; i++) {
uint8 high = uint8(strBytes[i * 2]);
uint8 low = uint8(strBytes[1 + i * 2]);
// Convert to lowercase if the character is in uppercase
if (high >= 65 && high <= 90) {
high += 32;
}
if (low >= 65 && low <= 90) {
low += 32;
}
uint8 digit = (high - (high >= 97 ? 87 : 48)) * 16 + (low - (low >= 97 ? 87 : 48));
addrBytes[i] = bytes1(digit);
}

assembly {
addr := mload(add(addrBytes, 20))
}

return addr;
}

// For XXXX => vIBC direction, SC needs to verify the proof of membership of TRY_PENDING
// For vIBC initiated channel, SC doesn't need to verify any proof, and these should be all empty
function isChannelOpenTry(CounterParty calldata counterparty) public pure returns (bool open) {
if (counterparty.channelId == bytes32(0) && bytes(counterparty.version).length == 0) {
return false;
// ChanOpenInit with unknow conterparty
} else if (counterparty.channelId != bytes32(0) && bytes(counterparty.version).length != 0) {
// this is the ChanOpenTry; counterparty must not be zero-value
return true;
} else {
revert IBCErrors.invalidCounterParty();
}
}

// convert params to UniversalPacketBytes with optimal gas cost

function toUniversalPacketBytes(UniversalPacket memory data) internal pure returns (bytes memory packetBytes) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import {ConsensusStateManager} from "../interfaces/ConsensusStateManager.sol";
import {L1Header, OpL2StateProof, Ics23Proof} from "../interfaces/ProofVerifier.sol";
import {LightClient, L1Header, OpL2StateProof, Ics23Proof} from "../interfaces/LightClient.sol";

/**
* @title DummyConsensusStateManager
* @title DummyLightClient
* @dev This contract is a dummy implementation of a consensus state manager.
* It should only be used for testing purposes.
* The logic for checking if the proof length is greater than zero is naive.
*/
contract DummyConsensusStateManager is ConsensusStateManager {
contract DummyLightClient is LightClient {
error InvalidDummyMembershipProof();
error InvalidDummyNonMembershipProof();

Expand Down
7 changes: 7 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,12 @@ fs_permissions = [{ access = 'read', path = './test/payload'}]
[fmt]
wrap_comments = true
number_underscore = "thousands"
ignore = ["lib/*"]

[rpc_endpoints]
sepolia = "${SEPOLIA_RPC_URL}"

[etherscan]
sepolia = { key = "${ETHERSCAN_API_KEY}" }

# See more config options https://book.getfoundry.sh/reference/config.html
1 change: 1 addition & 0 deletions lib/openzeppelin-contracts-upgradeable
1 change: 1 addition & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@openzeppelin/=lib/openzeppelin-contracts/
@openzeppelin-upgradeable/=lib/openzeppelin-contracts-upgradeable/
@lazyledger/protobuf3-solidity-lib/=lib/protobuf3-solidity-lib/
ds-test/=lib/forge-std/lib/ds-test/src/
forge-std/=lib/forge-std/src/
Expand Down
46 changes: 29 additions & 17 deletions script/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ pragma solidity ^0.8.0;

import "forge-std/Script.sol";
import "../contracts/utils/DummyProofVerifier.sol";
import "../contracts/utils/DummyConsensusStateManager.sol";
import "../contracts/utils/DummyLightClient.sol";
import "../contracts/core/Dispatcher.sol";
import "../contracts/examples/Mars.sol";
import {IDispatcher} from "../contracts/core/Dispatcher.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "../contracts/core/OpProofVerifier.sol";
import "../contracts/core/OpConsensusStateManager.sol";
import "../contracts/core/OpLightClient.sol";
import "../contracts/core/UniversalChannelHandler.sol";
import "../contracts/examples/Earth.sol";

Expand All @@ -31,7 +33,7 @@ contract Deploy is Script {

console.log("Deploying dummy contracts to %s...", chain);

address stateManager = deployDummyConsensusStateManager();
address stateManager = deployDummyLightClient();
address dispatcher = deployDispatcher(portPrefix, stateManager);
deployMars(dispatcher);

Expand All @@ -44,7 +46,7 @@ contract Deploy is Script {

address l1BlockProvider = vm.envOr("L1_BLOCK_PROVIDER", 0x4200000000000000000000000000000000000015);
uint32 fraudProofWindowSecs = 0;
address opStateManager = deployOpConsensusStateManager(fraudProofWindowSecs, proofVerifierAddr, l1BlockProvider);
address opStateManager = deployOpLightClient(fraudProofWindowSecs, proofVerifierAddr, l1BlockProvider);

dispatcher = deployDispatcher(portPrefix, opStateManager);
deployMars(dispatcher);
Expand All @@ -53,9 +55,9 @@ contract Deploy is Script {
deployEarth(universalChannelHandler);
}

function deployDummyConsensusStateManager() public broadcast returns (address addr_) {
DummyConsensusStateManager manager = new DummyConsensusStateManager{salt: _implSalt()}();
console.log("DummyConsensusStateManager deployed at %s", address(manager));
function deployDummyLightClient() public broadcast returns (address addr_) {
DummyLightClient manager = new DummyLightClient{salt: _implSalt()}();
console.log("DummyLightClient deployed at %s", address(manager));
return address(manager);
}

Expand All @@ -64,9 +66,19 @@ contract Deploy is Script {
broadcast
returns (address addr_)
{
Dispatcher dispatcher = new Dispatcher{salt: _implSalt()}(portPrefix, DummyConsensusStateManager(stateManager_));
console.log("Dispatcher deployed at %s", address(dispatcher));
return address(dispatcher);
Dispatcher impl = new Dispatcher{salt: _implSalt()}();
IDispatcher proxy = IDispatcher(
address(
new ERC1967Proxy{salt: _implSalt()}(
address(impl),
abi.encodeWithSelector(Dispatcher.initialize.selector, portPrefix, DummyLightClient(stateManager_))
)
)
);

console.log("Dispatcher imnplementation at %s", address(impl));
console.log("Dispatcher proxy at %s", address(proxy));
return address(proxy);
}

function deployMars(address dispatcher) public broadcast returns (address addr_) {
Expand All @@ -81,15 +93,15 @@ contract Deploy is Script {
return address(verifier);
}

function deployOpConsensusStateManager(
uint32 fraudProofWindowSecs,
address proofVerifierAddr,
address l1BlockProvider
) public broadcast returns (address addr_) {
OptimisticConsensusStateManager manager = new OptimisticConsensusStateManager{salt: _implSalt()}(
function deployOpLightClient(uint32 fraudProofWindowSecs, address proofVerifierAddr, address l1BlockProvider)
public
broadcast
returns (address addr_)
{
OptimisticLightClient manager = new OptimisticLightClient{salt: _implSalt()}(
fraudProofWindowSecs, ProofVerifier(proofVerifierAddr), L1Block(l1BlockProvider)
);
console.log("OptimisticConsensusStateManager deployed at %s", address(manager));
console.log("OptimisticLightClient deployed at %s", address(manager));
return address(manager);
}

Expand Down
Loading

0 comments on commit 7029199

Please sign in to comment.