diff --git a/.gitmodules b/.gitmodules index 38bad949..96e0aa72 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/contracts/core/Dispatcher.sol b/contracts/core/Dispatcher.sol index 4040a0c2..d0302891 100644 --- a/contracts/core/Dispatcher.sol +++ b/contracts/core/Dispatcher.sol @@ -4,12 +4,23 @@ pragma solidity ^0.8.9; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {IbcDispatcher, IbcEventsEmitter} from "../interfaces/IbcDispatcher.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +import {OwnableUpgradeable} from "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import {IbcChannelReceiver, IbcPacketReceiver} from "../interfaces/IbcReceiver.sol"; import {L1Header, OpL2StateProof, Ics23Proof} from "../interfaces/ProofVerifier.sol"; -import {ConsensusStateManager} from "../interfaces/ConsensusStateManager.sol"; +import {LightClient} from "../interfaces/LightClient.sol"; +import {IDispatcher} from "../interfaces/IDispatcher.sol"; import { - Channel, CounterParty, ChannelOrder, IbcPacket, ChannelState, AckPacket, IBCErrors, Ibc + Channel, + CounterParty, + ChannelOrder, + IbcPacket, + ChannelState, + AckPacket, + IBCErrors, + IbcUtils, + Ibc } from "../libs/Ibc.sol"; import {Address} from "@openzeppelin/contracts/utils/Address.sol"; @@ -20,31 +31,42 @@ import {Address} from "@openzeppelin/contracts/utils/Address.sol"; * Contract callers call this contract to send IBC-like msg, * which can be relayed to a rollup module on the Polymerase chain */ -contract Dispatcher is IbcDispatcher, IbcEventsEmitter, Ownable { +contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, IDispatcher { + // + // fields + // // IBC_PortID = portPrefix + address (hex string without 0x prefix, case insensitive) string public portPrefix; uint32 public portPrefixLen; - mapping(address => mapping(bytes32 => Channel)) public portChannelMap; - mapping(address => mapping(bytes32 => uint64)) public nextSequenceSend; + mapping(address => mapping(bytes32 => Channel)) private _portChannelMap; + mapping(address => mapping(bytes32 => uint64)) private _nextSequenceSend; // keep track of received packets' sequences to ensure channel ordering is enforced for ordered channels - mapping(address => mapping(bytes32 => uint64)) public nextSequenceRecv; - mapping(address => mapping(bytes32 => uint64)) public nextSequenceAck; + mapping(address => mapping(bytes32 => uint64)) private _nextSequenceRecv; + mapping(address => mapping(bytes32 => uint64)) private _nextSequenceAck; // only stores a bit to mark packet has not been ack'ed or timed out yet; actual IBC packet verification is done on // Polymer chain. // Keep track of sent packets - mapping(address => mapping(bytes32 => mapping(uint64 => bool))) public sendPacketCommitment; + mapping(address => mapping(bytes32 => mapping(uint64 => bool))) private _sendPacketCommitment; // keep track of received packets to prevent replay attack - mapping(address => mapping(bytes32 => mapping(uint64 => bool))) public recvPacketReceipt; + mapping(address => mapping(bytes32 => mapping(uint64 => bool))) private _recvPacketReceipt; // keep track of outbound ack packets to prevent replay attack - mapping(address => mapping(bytes32 => mapping(uint64 => bool))) public ackPacketCommitment; + mapping(address => mapping(bytes32 => mapping(uint64 => bool))) private _ackPacketCommitment; - ConsensusStateManager public consensusStateManager; + LightClient _lightClient; - constructor(string memory initPortPrefix, ConsensusStateManager _consensusStateManager) { + // + // methods + // + constructor() { + _disableInitializers(); + } + + function initialize(string memory initPortPrefix, LightClient lightClient) public initializer { + __Ownable_init(); portPrefix = initPortPrefix; portPrefixLen = uint32(bytes(initPortPrefix).length); - consensusStateManager = _consensusStateManager; + _lightClient = lightClient; } // @@ -65,7 +87,7 @@ contract Dispatcher is IbcDispatcher, IbcEventsEmitter, Ownable { uint256 height, uint256 appHash ) external returns (uint256 fraudProofEndTime, bool ended) { - return consensusStateManager.addOpConsensusState(l1header, proof, height, appHash); + return _lightClient.addOpConsensusState(l1header, proof, height, appHash); } /** @@ -116,7 +138,7 @@ contract Dispatcher is IbcDispatcher, IbcEventsEmitter, Ownable { revert IBCErrors.invalidCounterPartyPortId(); } - consensusStateManager.verifyMembership( + _lightClient.verifyMembership( proof, Ibc.channelProofKey(local.portId, local.channelId), Ibc.channelProofValue(ChannelState.TRY_PENDING, ordering, local.version, connectionHops, counterparty) @@ -154,7 +176,7 @@ contract Dispatcher is IbcDispatcher, IbcEventsEmitter, Ownable { CounterParty calldata counterparty, Ics23Proof calldata proof ) external { - consensusStateManager.verifyMembership( + _lightClient.verifyMembership( proof, Ibc.channelProofKey(local.portId, local.channelId), Ibc.channelProofValue(ChannelState.ACK_PENDING, ordering, local.version, connectionHops, counterparty) @@ -187,7 +209,7 @@ contract Dispatcher is IbcDispatcher, IbcEventsEmitter, Ownable { CounterParty calldata counterparty, Ics23Proof calldata proof ) external { - consensusStateManager.verifyMembership( + _lightClient.verifyMembership( proof, Ibc.channelProofKey(local.portId, local.channelId), Ibc.channelProofValue(ChannelState.CONFIRM_PENDING, ordering, local.version, connectionHops, counterparty) @@ -209,10 +231,10 @@ contract Dispatcher is IbcDispatcher, IbcEventsEmitter, Ownable { /** * @dev Emits a `CloseIbcChannel` event with the given `channelId` and the address of the message sender * @notice Close the specified IBC channel by channel ID - * Must be called by the channel owner, ie. portChannelMap[msg.sender][channelId] must exist + * Must be called by the channel owner, ie. _portChannelMap[msg.sender][channelId] must exist */ function closeIbcChannel(bytes32 channelId) external { - Channel memory channel = portChannelMap[msg.sender][channelId]; + Channel memory channel = _portChannelMap[msg.sender][channelId]; if (channel.counterpartyChannelId == bytes32(0)) { revert IBCErrors.channelNotOwnedBySender(); } @@ -241,14 +263,14 @@ contract Dispatcher is IbcDispatcher, IbcEventsEmitter, Ownable { // FIXME this is commented out to make the contract size smaller. We need to optimise for size // function onCloseIbcChannel(address portAddress, bytes32 channelId, Ics23Proof calldata proof) external { // // verify VIBC/IBC hub chain has processed ChanCloseConfirm event - // consensusStateManager.verifyMembership( + // _lightClient.verifyMembership( // proof, // bytes('channel/path/to/be/added/here'), // bytes('expected channel bytes constructed from params. Channel.State = {Closed(_Pending?)}') // ); // // // ensure port owns channel - // Channel memory channel = portChannelMap[portAddress][channelId]; + // Channel memory channel = _portChannelMap[portAddress][channelId]; // if (channel.counterpartyChannelId == bytes32(0)) { // revert channelNotOwnedByPortAddress(); // } @@ -256,7 +278,7 @@ contract Dispatcher is IbcDispatcher, IbcEventsEmitter, Ownable { // // confirm with dApp by calling its callback // IbcChannelReceiver reciever = IbcChannelReceiver(portAddress); // reciever.onCloseIbcChannel(channelId, channel.counterpartyPortId, channel.counterpartyChannelId); - // delete portChannelMap[portAddress][channelId]; + // delete _portChannelMap[portAddress][channelId]; // emit CloseIbcChannel(portAddress, channelId); // } @@ -277,7 +299,7 @@ contract Dispatcher is IbcDispatcher, IbcEventsEmitter, Ownable { */ function sendPacket(bytes32 channelId, bytes calldata packet, uint64 timeoutTimestamp) external { // ensure port owns channel - Channel memory channel = portChannelMap[msg.sender][channelId]; + Channel memory channel = _portChannelMap[msg.sender][channelId]; if (channel.counterpartyChannelId == bytes32(0)) { revert IBCErrors.channelNotOwnedBySender(); } @@ -308,15 +330,15 @@ contract Dispatcher is IbcDispatcher, IbcEventsEmitter, Ownable { } // prove ack packet is on Polymer chain - consensusStateManager.verifyMembership(proof, Ibc.ackProofKey(packet), abi.encode(Ibc.ackProofValue(ack))); + _lightClient.verifyMembership(proof, Ibc.ackProofKey(packet), abi.encode(Ibc.ackProofValue(ack))); // verify packet has been committed and not yet ack'ed or timed out - bool hasCommitment = sendPacketCommitment[address(receiver)][packet.src.channelId][packet.sequence]; + bool hasCommitment = _sendPacketCommitment[address(receiver)][packet.src.channelId][packet.sequence]; if (!hasCommitment) { revert IBCErrors.packetCommitmentNotFound(); } // enforce ack'ed packet sequences always increment by 1 for ordered channels - Channel memory channel = portChannelMap[address(receiver)][packet.src.channelId]; + Channel memory channel = _portChannelMap[address(receiver)][packet.src.channelId]; (bool success, bytes memory data) = _callIfContract( address(receiver), abi.encodeWithSelector(IbcPacketReceiver.onAcknowledgementPacket.selector, packet, Ibc.parseAckData(ack)) @@ -324,15 +346,15 @@ contract Dispatcher is IbcDispatcher, IbcEventsEmitter, Ownable { if (success) { if (channel.ordering == ChannelOrder.ORDERED) { - if (packet.sequence != nextSequenceAck[address(receiver)][packet.src.channelId]) { + if (packet.sequence != _nextSequenceAck[address(receiver)][packet.src.channelId]) { revert IBCErrors.unexpectedPacketSequence(); } - nextSequenceAck[address(receiver)][packet.src.channelId] = packet.sequence + 1; + _nextSequenceAck[address(receiver)][packet.src.channelId] = packet.sequence + 1; } // delete packet commitment to avoid double ack - delete sendPacketCommitment[address(receiver)][packet.src.channelId][packet.sequence]; + delete _sendPacketCommitment[address(receiver)][packet.src.channelId][packet.sequence]; emit Acknowledgement(address(receiver), packet.src.channelId, packet.sequence); } else { emit AcknowledgementError(address(receiver), data); @@ -357,10 +379,10 @@ contract Dispatcher is IbcDispatcher, IbcEventsEmitter, Ownable { // prove absence of packet receipt on Polymer chain // TODO: add non membership support - consensusStateManager.verifyNonMembership(proof, "packet/receipt/path"); + _lightClient.verifyNonMembership(proof, "packet/receipt/path"); // verify packet has been committed and not yet ack'ed or timed out - bool hasCommitment = sendPacketCommitment[address(receiver)][packet.src.channelId][packet.sequence]; + bool hasCommitment = _sendPacketCommitment[address(receiver)][packet.src.channelId][packet.sequence]; if (!hasCommitment) { revert IBCErrors.packetCommitmentNotFound(); } @@ -370,7 +392,7 @@ contract Dispatcher is IbcDispatcher, IbcEventsEmitter, Ownable { ); if (success) { // delete packet commitment to avoid double timeout - delete sendPacketCommitment[address(receiver)][packet.src.channelId][packet.sequence]; + delete _sendPacketCommitment[address(receiver)][packet.src.channelId][packet.sequence]; emit Timeout(address(receiver), packet.src.channelId, packet.sequence); } else { emit TimeoutError(address(receiver), data); @@ -393,26 +415,26 @@ contract Dispatcher is IbcDispatcher, IbcEventsEmitter, Ownable { if (!portIdAddressMatch(address(receiver), packet.dest.portId)) { revert IBCErrors.receiverNotIntendedPacketDestination(); } - consensusStateManager.verifyMembership( + _lightClient.verifyMembership( proof, Ibc.packetCommitmentProofKey(packet), abi.encode(Ibc.packetCommitmentProofValue(packet)) ); // verify packet has not been received yet - bool hasReceipt = recvPacketReceipt[address(receiver)][packet.dest.channelId][packet.sequence]; + bool hasReceipt = _recvPacketReceipt[address(receiver)][packet.dest.channelId][packet.sequence]; if (hasReceipt) { revert IBCErrors.packetReceiptAlreadyExists(); } - recvPacketReceipt[address(receiver)][packet.dest.channelId][packet.sequence] = true; + _recvPacketReceipt[address(receiver)][packet.dest.channelId][packet.sequence] = true; // enforce recv'ed packet sequences always increment by 1 for ordered channels - Channel memory channel = portChannelMap[address(receiver)][packet.dest.channelId]; + Channel memory channel = _portChannelMap[address(receiver)][packet.dest.channelId]; if (channel.ordering == ChannelOrder.ORDERED) { - if (packet.sequence != nextSequenceRecv[address(receiver)][packet.dest.channelId]) { + if (packet.sequence != _nextSequenceRecv[address(receiver)][packet.dest.channelId]) { revert IBCErrors.unexpectedPacketSequence(); } - nextSequenceRecv[address(receiver)][packet.dest.channelId] = packet.sequence + 1; + _nextSequenceRecv[address(receiver)][packet.dest.channelId] = packet.sequence + 1; } // Emit recv packet event to prove the relayer did the correct job, and pkt is received. @@ -437,13 +459,13 @@ contract Dispatcher is IbcDispatcher, IbcEventsEmitter, Ownable { } else { ack = AckPacket(false, data); } - bool hasAckPacketCommitment = ackPacketCommitment[address(receiver)][packet.dest.channelId][packet.sequence]; + bool hasAckPacketCommitment = _ackPacketCommitment[address(receiver)][packet.dest.channelId][packet.sequence]; // check is not necessary for sync-acks if (hasAckPacketCommitment) { revert IBCErrors.ackPacketCommitmentAlreadyExists(); } - ackPacketCommitment[address(receiver)][packet.dest.channelId][packet.sequence] = true; + _ackPacketCommitment[address(receiver)][packet.dest.channelId][packet.sequence] = true; emit WriteAckPacket(address(receiver), packet.dest.channelId, packet.sequence, ack); } @@ -461,7 +483,7 @@ contract Dispatcher is IbcDispatcher, IbcEventsEmitter, Ownable { // TODO: remove below writeTimeoutPacket() function // 1. core SC is responsible to generate timeout packet // 2. user contract are not free to generate timeout with different criteria - // 3. [optional]: we may wish relayer to trigger timeout process, but in this case, below function won't do + // 3. [optional]: we may wish relayer to trigger timeout process, but in this case, belowunction won't do // the job, as it doesn't have proofs. // There is no strong reason to do this, as relayer can always do the regular `recvPacket` flow, which will // do proper timeout generation. @@ -475,7 +497,7 @@ contract Dispatcher is IbcDispatcher, IbcEventsEmitter, Ownable { } // verify packet does not have a receipt - bool hasReceipt = recvPacketReceipt[receiver][packet.dest.channelId][packet.sequence]; + bool hasReceipt = _recvPacketReceipt[receiver][packet.dest.channelId][packet.sequence]; if (hasReceipt) { revert IBCErrors.packetReceiptAlreadyExists(); } @@ -499,7 +521,7 @@ contract Dispatcher is IbcDispatcher, IbcEventsEmitter, Ownable { * values per EVM. */ function getChannel(address portAddress, bytes32 channelId) external view returns (Channel memory channel) { - channel = portChannelMap[portAddress][channelId]; + channel = _portChannelMap[portAddress][channelId]; } // getOptimisticConsensusState @@ -508,7 +530,7 @@ contract Dispatcher is IbcDispatcher, IbcEventsEmitter, Ownable { view returns (uint256 appHash, uint256 fraudProofEndTime, bool ended) { - return consensusStateManager.getState(height); + return _lightClient.getState(height); } // verify an EVM address matches an IBC portId. @@ -524,15 +546,15 @@ contract Dispatcher is IbcDispatcher, IbcEventsEmitter, Ownable { // Prerequisite: must verify sender is authorized to send packet on the channel function _sendPacket(address sender, bytes32 channelId, bytes memory packet, uint64 timeoutTimestamp) internal { // current packet sequence - uint64 sequence = nextSequenceSend[sender][channelId]; + uint64 sequence = _nextSequenceSend[sender][channelId]; if (sequence == 0) { revert IBCErrors.invalidPacketSequence(); } // packet commitment - sendPacketCommitment[sender][channelId][sequence] = true; + _sendPacketCommitment[sender][channelId][sequence] = true; // increment nextSendPacketSequence - nextSequenceSend[sender][channelId] = sequence + 1; + _nextSequenceSend[sender][channelId] = sequence + 1; emit SendPacket(sender, channelId, packet, sequence, timeoutTimestamp); } @@ -549,7 +571,7 @@ contract Dispatcher is IbcDispatcher, IbcEventsEmitter, Ownable { // TODO: check duplicated channel registration? // TODO: The call to `Channel` constructor MUST be move to `openIbcChannel` phase // Then `connectIbcChannel` phase can use the `version` as part of `require` condition. - portChannelMap[address(portAddress)][local.channelId] = Channel( + _portChannelMap[address(portAddress)][local.channelId] = Channel( counterparty.version, // TODO: this should be self version instead of counterparty version ordering, feeEnabled, @@ -559,9 +581,9 @@ contract Dispatcher is IbcDispatcher, IbcEventsEmitter, Ownable { ); // initialize channel sequences - nextSequenceSend[address(portAddress)][local.channelId] = 1; - nextSequenceRecv[address(portAddress)][local.channelId] = 1; - nextSequenceAck[address(portAddress)][local.channelId] = 1; + _nextSequenceSend[address(portAddress)][local.channelId] = 1; + _nextSequenceRecv[address(portAddress)][local.channelId] = 1; + _nextSequenceAck[address(portAddress)][local.channelId] = 1; } // Returns the result of the call if no revert, otherwise returns the error if thrown. @@ -578,6 +600,8 @@ contract Dispatcher is IbcDispatcher, IbcEventsEmitter, Ownable { (success, message) = receiver.call(args); } + function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} + // _isPacketTimeout returns true if the given packet has timed out acoording to host chain's block height and // timestamp function _isPacketTimeout(IbcPacket calldata packet) internal view returns (bool isTimeOut) { diff --git a/contracts/core/OpConsensusStateManager.sol b/contracts/core/OpLightClient.sol similarity index 95% rename from contracts/core/OpConsensusStateManager.sol rename to contracts/core/OpLightClient.sol index 96619112..5e760bb8 100644 --- a/contracts/core/OpConsensusStateManager.sol +++ b/contracts/core/OpLightClient.sol @@ -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; diff --git a/contracts/interfaces/IDispatcher.sol b/contracts/interfaces/IDispatcher.sol new file mode 100644 index 00000000..f89666f2 --- /dev/null +++ b/contracts/interfaces/IDispatcher.sol @@ -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); +} diff --git a/contracts/interfaces/ConsensusStateManager.sol b/contracts/interfaces/LightClient.sol similarity index 97% rename from contracts/interfaces/ConsensusStateManager.sol rename to contracts/interfaces/LightClient.sol index ced2a9c2..e93e4123 100644 --- a/contracts/interfaces/ConsensusStateManager.sol +++ b/contracts/interfaces/LightClient.sol @@ -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 diff --git a/contracts/libs/Ibc.sol b/contracts/libs/Ibc.sol index 92aaaf03..fcffda3b 100644 --- a/contracts/libs/Ibc.sol +++ b/contracts/libs/Ibc.sol @@ -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) { diff --git a/contracts/utils/DummyConsensusStateManager.sol b/contracts/utils/DummyLightClient.sol similarity index 83% rename from contracts/utils/DummyConsensusStateManager.sol rename to contracts/utils/DummyLightClient.sol index 9bfa6b3d..daf657d1 100644 --- a/contracts/utils/DummyConsensusStateManager.sol +++ b/contracts/utils/DummyLightClient.sol @@ -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(); diff --git a/foundry.toml b/foundry.toml index 4f67570f..2ebe000f 100644 --- a/foundry.toml +++ b/foundry.toml @@ -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 diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable new file mode 160000 index 00000000..2d081f24 --- /dev/null +++ b/lib/openzeppelin-contracts-upgradeable @@ -0,0 +1 @@ +Subproject commit 2d081f24cac1a867f6f73d512f2022e1fa987854 diff --git a/remappings.txt b/remappings.txt index e5fa5bf5..c746ca4f 100644 --- a/remappings.txt +++ b/remappings.txt @@ -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/ diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 1a006536..033f6bcd 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -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"; @@ -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); @@ -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); @@ -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); } @@ -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_) { @@ -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); } diff --git a/test/Dispatcher.base.t.sol b/test/Dispatcher.base.t.sol index f2065885..a70b9b72 100644 --- a/test/Dispatcher.base.t.sol +++ b/test/Dispatcher.base.t.sol @@ -2,15 +2,17 @@ pragma solidity ^0.8.13; import "forge-std/Test.sol"; -import "./Proof.base.t.sol"; +import {ProofBase} from "./Proof.base.t.sol"; import "../contracts/libs/Ibc.sol"; import {Dispatcher} from "../contracts/core/Dispatcher.sol"; +import {IDispatcher} from "../contracts/interfaces/IDispatcher.sol"; import {IbcEventsEmitter} from "../contracts/interfaces/IbcDispatcher.sol"; import {IbcChannelReceiver} from "../contracts/interfaces/IbcReceiver.sol"; import "../contracts/examples/Mars.sol"; -import "../contracts/core/OpConsensusStateManager.sol"; -import "../contracts/utils/DummyConsensusStateManager.sol"; +import "../contracts/core/OpLightClient.sol"; +import "../contracts/utils/DummyLightClient.sol"; import "../contracts/core/OpProofVerifier.sol"; +import {TestUtilsTest} from "./TestUtils.t.sol"; struct LocalEnd { IbcChannelReceiver receiver; @@ -30,18 +32,18 @@ struct ChannelHandshakeSetting { } // Base contract for testing Dispatcher -contract Base is IbcEventsEmitter, ProofBase { +contract Base is IbcEventsEmitter, ProofBase, TestUtilsTest { uint64 UINT64_MAX = 18_446_744_073_709_551_615; Height ZERO_HEIGHT = Height(0, 0); uint64 maxTimeout = UINT64_MAX; - ConsensusStateManager opConsensusStateManager = - new OptimisticConsensusStateManager(1800, opProofVerifier, l1BlockProvider); + LightClient opLightClient = new OptimisticLightClient(1800, opProofVerifier, l1BlockProvider); - ConsensusStateManager dummyConsStateManager = new DummyConsensusStateManager(); + LightClient dummyConsStateManager = new DummyLightClient(); - Dispatcher dispatcher; + IDispatcher public dispatcherProxy; + Dispatcher public dispatcherImplementation; string portPrefix = "polyibc.eth."; string[] connectionHops = ["connection-1", "connection-2"]; @@ -81,7 +83,9 @@ contract Base is IbcEventsEmitter, ProofBase { address(le.receiver), le.versionExpected, s.ordering, s.feeEnabled, le.connectionHops, re.portId ); } - dispatcher.channelOpenInit(le.receiver, le.versionCall, s.ordering, s.feeEnabled, le.connectionHops, re.portId); + dispatcherProxy.channelOpenInit( + le.receiver, le.versionCall, s.ordering, s.feeEnabled, le.connectionHops, re.portId + ); } /** @@ -108,7 +112,7 @@ contract Base is IbcEventsEmitter, ProofBase { ); } CounterParty memory cp = CounterParty(re.portId, re.channelId, re.version); - dispatcher.channelOpenTry( + dispatcherProxy.channelOpenTry( le.receiver, CounterParty(le.portId, le.channelId, le.versionCall), s.ordering, @@ -134,7 +138,7 @@ contract Base is IbcEventsEmitter, ProofBase { vm.expectEmit(true, true, true, true); emit ChannelOpenAck(address(le.receiver), le.channelId); } - dispatcher.channelOpenAck( + dispatcherProxy.channelOpenAck( le.receiver, CounterParty(le.portId, le.channelId, le.versionCall), le.connectionHops, @@ -163,7 +167,7 @@ contract Base is IbcEventsEmitter, ProofBase { vm.expectEmit(true, true, true, true); emit ChannelOpenConfirm(address(le.receiver), le.channelId); } - dispatcher.channelOpenConfirm( + dispatcherProxy.channelOpenConfirm( le.receiver, CounterParty(le.portId, le.channelId, le.versionCall), le.connectionHops, diff --git a/test/Dispatcher.client.t.sol b/test/Dispatcher.client.t.sol index 53ef0b7f..795997f6 100644 --- a/test/Dispatcher.client.t.sol +++ b/test/Dispatcher.client.t.sol @@ -3,33 +3,30 @@ pragma solidity ^0.8.13; import "../contracts/libs/Ibc.sol"; import {Dispatcher} from "../contracts/core/Dispatcher.sol"; +import {IDispatcher} from "../contracts/interfaces/IDispatcher.sol"; import {IbcEventsEmitter} from "../contracts/interfaces/IbcDispatcher.sol"; import {IbcReceiver} from "../contracts/interfaces/IbcReceiver.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import "../contracts/examples/Mars.sol"; -import "../contracts/core/OpConsensusStateManager.sol"; +import "../contracts/core/OpLightClient.sol"; import "./Dispatcher.base.t.sol"; -contract ClientTestBase is Base { - function setUp() public virtual override { - super.setUp(); - dispatcher = new Dispatcher(portPrefix, opConsensusStateManager); - } -} - -contract DispatcherUpdateClientTest is ClientTestBase { - function setUp() public override { - super.setUp(); - } - +abstract contract DispatcherUpdateClientTestSuite is Base { function test_updateOptimisticConsensusState_success() public { // trick the L1Block contract into thinking it is updated with the right l1 header setL1BlockAttributes(keccak256(RLPWriter.writeList(l1header.header)), l1header.number); - dispatcher.updateClientWithOptimisticConsensusState(l1header, validStateProof, 1, uint256(apphash)); + dispatcherProxy.updateClientWithOptimisticConsensusState(l1header, validStateProof, 1, uint256(apphash)); } function test_updateOptimisticConsensusState_failure() public { setL1BlockAttributes(keccak256(RLPWriter.writeList(l1header.header)), l1header.number); vm.expectRevert("MerkleTrie: ran out of proof elements"); - dispatcher.updateClientWithOptimisticConsensusState(l1header, invalidStateProof, 1, uint256(apphash)); + dispatcherProxy.updateClientWithOptimisticConsensusState(l1header, invalidStateProof, 1, uint256(apphash)); + } +} + +contract DispatcherUpdateClientTest is DispatcherUpdateClientTestSuite { + function setUp() public virtual override { + (dispatcherProxy, dispatcherImplementation) = deployDispatcherProxyAndImpl(portPrefix, opLightClient); } } diff --git a/test/Dispatcher.proof.t.sol b/test/Dispatcher.proof.t.sol index 2f06baa3..f0caca6d 100644 --- a/test/Dispatcher.proof.t.sol +++ b/test/Dispatcher.proof.t.sol @@ -2,19 +2,20 @@ pragma solidity ^0.8.15; import "../contracts/libs/Ibc.sol"; +import {Base} from "./Dispatcher.base.t.sol"; import {Dispatcher} from "../contracts/core/Dispatcher.sol"; -import {Mars} from "../contracts/examples/Mars.sol"; +import {IDispatcher} from "../contracts/interfaces/IDispatcher.sol"; +import "../contracts/examples/Mars.sol"; import {IbcDispatcher, IbcEventsEmitter} from "../contracts/interfaces/IbcDispatcher.sol"; -import "../contracts/core/OpConsensusStateManager.sol"; +import "../contracts/core/OpLightClient.sol"; import "./Proof.base.t.sol"; import {stdStorage, StdStorage} from "forge-std/Test.sol"; -using stdStorage for StdStorage; +abstract contract DispatcherIbcWithRealProofsSuite is IbcEventsEmitter, Base { + using stdStorage for StdStorage; -contract DispatcherIbcWithRealProofs is IbcEventsEmitter, ProofBase { Mars mars; - Dispatcher dispatcher; - OptimisticConsensusStateManager consensusStateManager; + OptimisticLightClient consensusStateManager; CounterParty ch0 = CounterParty("polyibc.eth1.71C95911E9a5D330f4D621842EC243EE1343292e", IbcUtils.toBytes32("channel-0"), "1.0"); @@ -23,19 +24,12 @@ contract DispatcherIbcWithRealProofs is IbcEventsEmitter, ProofBase { string[] connectionHops0 = ["connection-0", "connection-3"]; string[] connectionHops1 = ["connection-2", "connection-1"]; - function setUp() public override { - super.setUp(); - consensusStateManager = new OptimisticConsensusStateManager(1, opProofVerifier, l1BlockProvider); - dispatcher = new Dispatcher("polyibc.eth1.", consensusStateManager); - mars = new Mars(dispatcher); - } - function test_ibc_channel_open_init() public { vm.expectEmit(true, true, true, true); emit ChannelOpenInit(address(mars), "1.0", ChannelOrder.NONE, false, connectionHops1, ch1.portId); // since this is open chann init, the proof is not used. so use an invalid one - dispatcher.channelOpenInit(mars, ch1.version, ChannelOrder.NONE, false, connectionHops1, ch1.portId); + dispatcherProxy.channelOpenInit(mars, ch1.version, ChannelOrder.NONE, false, connectionHops1, ch1.portId); } function test_ibc_channel_open_try() public { @@ -44,7 +38,7 @@ contract DispatcherIbcWithRealProofs is IbcEventsEmitter, ProofBase { vm.expectEmit(true, true, true, true); emit ChannelOpenTry(address(mars), "1.0", ChannelOrder.NONE, false, connectionHops1, ch0.portId, ch0.channelId); - dispatcher.channelOpenTry(mars, ch1, ChannelOrder.NONE, false, connectionHops1, ch0, proof); + dispatcherProxy.channelOpenTry(mars, ch1, ChannelOrder.NONE, false, connectionHops1, ch0, proof); } function test_ibc_channel_ack() public { @@ -53,7 +47,7 @@ contract DispatcherIbcWithRealProofs is IbcEventsEmitter, ProofBase { vm.expectEmit(true, true, true, true); emit ChannelOpenAck(address(mars), ch0.channelId); - dispatcher.channelOpenAck(mars, ch0, connectionHops0, ChannelOrder.NONE, false, ch1, proof); + dispatcherProxy.channelOpenAck(mars, ch0, connectionHops0, ChannelOrder.NONE, false, ch1, proof); } function test_ibc_channel_confirm() public { @@ -62,18 +56,19 @@ contract DispatcherIbcWithRealProofs is IbcEventsEmitter, ProofBase { vm.expectEmit(true, true, true, true); emit ChannelOpenConfirm(address(mars), ch1.channelId); - dispatcher.channelOpenConfirm(mars, ch1, connectionHops1, ChannelOrder.NONE, false, ch0, proof); + dispatcherProxy.channelOpenConfirm(mars, ch1, connectionHops1, ChannelOrder.NONE, false, ch0, proof); } function test_ack_packet() public { Ics23Proof memory proof = load_proof("/test/payload/packet_ack_proof.hex"); // plant a fake packet commitment so the ack checks go through - // use "forge inspect --storage" to find the slot 1 - bytes32 slot1 = keccak256(abi.encode(address(mars), uint32(7))); // current nested mapping slot: 107 + // Stdstore doesn't work for proxies so we have to use store + // use "forge inspect --storage" to find the nested mapping slot + bytes32 slot1 = keccak256(abi.encode(address(mars), uint32(107))); // current nested mapping slot: 107 bytes32 slot2 = keccak256(abi.encode(ch0.channelId, slot1)); bytes32 slot3 = keccak256(abi.encode(uint256(1), slot2)); - vm.store(address(dispatcher), slot3, bytes32(uint256(1))); + vm.store(address(dispatcherProxy), slot3, bytes32(uint256(1))); IbcPacket memory packet; packet.data = bytes("packet-1"); @@ -90,7 +85,8 @@ contract DispatcherIbcWithRealProofs is IbcEventsEmitter, ProofBase { vm.expectEmit(true, true, true, true); emit Acknowledgement(address(mars), packet.src.channelId, packet.sequence); - dispatcher.acknowledgement(mars, packet, ack, proof); + + dispatcherProxy.acknowledgement(mars, packet, ack, proof); } function test_recv_packet() public { @@ -113,7 +109,7 @@ contract DispatcherIbcWithRealProofs is IbcEventsEmitter, ProofBase { packet.sequence, AckPacket(true, abi.encodePacked('{ "account": "account", "reply": "got the message" }')) ); - dispatcher.recvPacket(mars, packet, proof); + dispatcherProxy.recvPacket(mars, packet, proof); } function test_timeout_packet() public { @@ -134,3 +130,13 @@ contract DispatcherIbcWithRealProofs is IbcEventsEmitter, ProofBase { return proof; } } + +contract DispatcherIbcWithRealProofs is DispatcherIbcWithRealProofsSuite { + function setUp() public override { + super.setUp(); + consensusStateManager = new OptimisticLightClient(1, opProofVerifier, l1BlockProvider); + (dispatcherProxy, dispatcherImplementation) = + deployDispatcherProxyAndImpl("polyibc.eth1.", consensusStateManager); + mars = new Mars(dispatcherProxy); + } +} diff --git a/test/Dispatcher.t.sol b/test/Dispatcher.t.sol index 70c2c663..7d563e27 100644 --- a/test/Dispatcher.t.sol +++ b/test/Dispatcher.t.sol @@ -4,25 +4,49 @@ pragma solidity ^0.8.13; import "../contracts/libs/Ibc.sol"; import {Dispatcher} from "../contracts/core/Dispatcher.sol"; import {IbcEventsEmitter} from "../contracts/interfaces/IbcDispatcher.sol"; -import {IbcReceiver, IbcReceiverBase} from "../contracts/interfaces/IbcReceiver.sol"; +import {IbcReceiver} from "../contracts/interfaces/IbcReceiver.sol"; +import {DummyLightClient} from "../contracts/utils/DummyLightClient.sol"; import "../contracts/examples/Mars.sol"; -import "../contracts/core/OpConsensusStateManager.sol"; +import "../contracts/core/OpLightClient.sol"; import "./Dispatcher.base.t.sol"; import {Earth} from "../contracts/examples/Earth.sol"; -contract ChannelHandshakeTest is Base { +abstract contract ChannelHandshakeUtils is Base { string portId = "eth1.7E5F4552091A69125d5DfCb7b8C2659029395Bdf"; LocalEnd _local; - CounterParty _remote; Mars mars; + CounterParty _remote; - function setUp() public override { - dispatcher = new Dispatcher(portPrefix, dummyConsStateManager); - mars = new Mars(dispatcher); - _local = LocalEnd(mars, portId, "channel-1", connectionHops, "1.0", "1.0"); - _remote = CounterParty("eth2.7E5F4552091A69125d5DfCb7b8C2659029395Bdf", "channel-2", "1.0"); + function createSettings(bool localInitiate, bool isProofValid) + internal + view + returns (ChannelHandshakeSetting[4] memory) + { + Ics23Proof memory proof = isProofValid ? validProof : invalidProof; + ChannelHandshakeSetting[4] memory settings = [ + ChannelHandshakeSetting(ChannelOrder.ORDERED, false, localInitiate, proof), + ChannelHandshakeSetting(ChannelOrder.UNORDERED, false, localInitiate, proof), + ChannelHandshakeSetting(ChannelOrder.ORDERED, true, localInitiate, proof), + ChannelHandshakeSetting(ChannelOrder.UNORDERED, true, localInitiate, proof) + ]; + return settings; } + function createSettings2(bool isProofValid) internal view returns (ChannelHandshakeSetting[8] memory) { + // localEnd initiates + ChannelHandshakeSetting[4] memory settings1 = createSettings(true, isProofValid); + // remoteEnd initiates + ChannelHandshakeSetting[4] memory settings2 = createSettings(false, isProofValid); + ChannelHandshakeSetting[8] memory settings; + for (uint256 i = 0; i < settings1.length; i++) { + settings[i] = settings1[i]; + settings[i + settings1.length] = settings2[i]; + } + return settings; + } +} + +abstract contract ChannelHandshakeTestSuite is ChannelHandshakeUtils { function test_openChannel_initiator_ok() public { ChannelHandshakeSetting[4] memory settings = createSettings(true, true); string[2] memory versions = ["1.0", "2.0"]; @@ -106,8 +130,7 @@ contract ChannelHandshakeTest is Base { CounterParty memory re = _remote; le.versionCall = versions[j]; le.versionExpected = versions[j]; - - vm.expectRevert(DummyConsensusStateManager.InvalidDummyMembershipProof.selector); + vm.expectRevert(DummyLightClient.InvalidDummyMembershipProof.selector); channelOpenTry(le, re, settings[i], false); } } @@ -148,44 +171,24 @@ contract ChannelHandshakeTest is Base { channelOpenTry(le, re, settings[i], true); re.version = versions[j]; settings[i].proof = invalidProof; - - vm.expectRevert(DummyConsensusStateManager.InvalidDummyMembershipProof.selector); + vm.expectRevert(DummyLightClient.InvalidDummyMembershipProof.selector); channelOpenAck(le, re, settings[i], false); } } } +} - function createSettings(bool localInitiate, bool isProofValid) - internal - view - returns (ChannelHandshakeSetting[4] memory) - { - Ics23Proof memory proof = isProofValid ? validProof : invalidProof; - ChannelHandshakeSetting[4] memory settings = [ - ChannelHandshakeSetting(ChannelOrder.ORDERED, false, localInitiate, proof), - ChannelHandshakeSetting(ChannelOrder.UNORDERED, false, localInitiate, proof), - ChannelHandshakeSetting(ChannelOrder.ORDERED, true, localInitiate, proof), - ChannelHandshakeSetting(ChannelOrder.UNORDERED, true, localInitiate, proof) - ]; - return settings; - } - - function createSettings2(bool isProofValid) internal view returns (ChannelHandshakeSetting[8] memory) { - // localEnd initiates - ChannelHandshakeSetting[4] memory settings1 = createSettings(true, isProofValid); - // remoteEnd initiates - ChannelHandshakeSetting[4] memory settings2 = createSettings(false, isProofValid); - ChannelHandshakeSetting[8] memory settings; - for (uint256 i = 0; i < settings1.length; i++) { - settings[i] = settings1[i]; - settings[i + settings1.length] = settings2[i]; - } - return settings; +contract ChannelHandshakeTest is ChannelHandshakeTestSuite { + function setUp() public virtual override { + (dispatcherProxy, dispatcherImplementation) = deployDispatcherProxyAndImpl(portPrefix, dummyConsStateManager); + mars = new Mars(dispatcherProxy); + _local = LocalEnd(mars, portId, "channel-1", connectionHops, "1.0", "1.0"); + _remote = CounterParty("eth2.7E5F4552091A69125d5DfCb7b8C2659029395Bdf", "channel-2", "1.0"); } } // This Base contract provides an open channel for sub-contract tests -contract ChannelOpenTestBase is Base { +contract ChannelOpenTestBaseSetup is Base { string portId = "eth1.7E5F4552091A69125d5DfCb7b8C2659029395Bdf"; string invalidPortId = "eth1.0xd6292A04e605AFf917Bf05b2df5dDdbdc3E35e07"; bytes32 channelId = "channel-1"; @@ -199,15 +202,15 @@ contract ChannelOpenTestBase is Base { RevertingBytesMars revertingBytesMars; function setUp() public virtual override { - dispatcher = new Dispatcher(portPrefix, dummyConsStateManager); + (dispatcherProxy, dispatcherImplementation) = deployDispatcherProxyAndImpl(portPrefix, dummyConsStateManager); ChannelHandshakeSetting memory setting = ChannelHandshakeSetting(ChannelOrder.ORDERED, feeEnabled, true, validProof); // anyone can run Relayers vm.startPrank(relayer); vm.deal(relayer, 100_000 ether); - mars = new Mars(dispatcher); - revertingBytesMars = new RevertingBytesMars(dispatcher); + mars = new Mars(dispatcherProxy); + revertingBytesMars = new RevertingBytesMars(dispatcherProxy); _local = LocalEnd(mars, portId, channelId, connectionHops, "1.0", "1.0"); _localRevertingMars = LocalEnd(revertingBytesMars, portId, channelId, connectionHops, "1.0", "1.0"); @@ -230,7 +233,7 @@ contract ChannelOpenTestBase is Base { // } // // function test_closeChannelInit_mustOwner() public { -// Mars earth = new Mars(dispatcher); +// Mars earth = new Mars(dispatcherProxy); // vm.expectRevert(abi.encodeWithSignature('channelNotOwnedBySender()')); // earth.triggerChannelClose(channelId); // } @@ -238,21 +241,21 @@ contract ChannelOpenTestBase is Base { // function test_closeChannelConfirm_success() public { // vm.expectEmit(true, true, true, true); // emit CloseIbcChannel(address(mars), channelId); -// dispatcher.onCloseIbcChannel(address(mars), channelId, validProof); +// dispatcherProxy.onCloseIbcChannel(address(mars), channelId, validProof); // } // // function test_closeChannelConfirm_mustOwner() public { // vm.expectRevert(abi.encodeWithSignature('channelNotOwnedByPortAddress()')); -// dispatcher.onCloseIbcChannel(address(mars), 'channel-999', validProof); +// dispatcherProxy.onCloseIbcChannel(address(mars), 'channel-999', validProof); // } // // function test_closeChannelConfirm_invalidProof() public { // vm.expectRevert('Invalid dummy membership proof'); -// dispatcher.onCloseIbcChannel(address(mars), channelId, invalidProof); +// dispatcherProxy.onCloseIbcChannel(address(mars), channelId, invalidProof); // } // } -contract DispatcherSendPacketTest is ChannelOpenTestBase { +contract DispatcherSendPacketTestSuite is ChannelOpenTestBaseSetup { // default params string payload = "msgPayload"; uint64 timeoutTimestamp = 1000; @@ -269,13 +272,13 @@ contract DispatcherSendPacketTest is ChannelOpenTestBase { // sendPacket fails if calling dApp doesn't own the channel function test_mustOwner() public { - Mars earth = new Mars(dispatcher); + Mars earth = new Mars(dispatcherProxy); vm.expectRevert(abi.encodeWithSignature("channelNotOwnedBySender()")); earth.greet(payload, channelId, timeoutTimestamp); } } -contract PacketSenderTestBase is ChannelOpenTestBase { +contract PacketSenderTestBase is ChannelOpenTestBaseSetup { IbcEndpoint dest = IbcEndpoint("polyibc.bsc.9876543210", "channel-99"); IbcEndpoint src; IbcEndpoint srcRevertingMars; @@ -319,7 +322,7 @@ contract PacketSenderTestBase is ChannelOpenTestBase { } // Test Chains B receives a packet from Chain A -contract DispatcherRecvPacketTest is ChannelOpenTestBase { +contract DispatcherRecvPacketTestSuite is ChannelOpenTestBaseSetup { IbcEndpoint src = IbcEndpoint("polyibc.bsc.9876543210", "channel-99"); IbcEndpoint dest; bytes payload = bytes("msgPayload"); @@ -334,11 +337,11 @@ contract DispatcherRecvPacketTest is ChannelOpenTestBase { function test_success() public { for (uint64 index = 0; index < 3; index++) { uint64 packetSeq = index + 1; - vm.expectEmit(true, true, true, true, address(dispatcher)); + vm.expectEmit(true, true, true, true, address(dispatcherProxy)); emit RecvPacket(address(mars), channelId, packetSeq); - vm.expectEmit(true, true, false, true, address(dispatcher)); + vm.expectEmit(true, true, false, true, address(dispatcherProxy)); emit WriteAckPacket(address(mars), channelId, packetSeq, AckPacket(true, appAck)); - dispatcher.recvPacket( + dispatcherProxy.recvPacket( IbcReceiver(mars), IbcPacket(src, dest, packetSeq, payload, ZERO_HEIGHT, maxTimeout), validProof ); } @@ -348,43 +351,47 @@ contract DispatcherRecvPacketTest is ChannelOpenTestBase { function test_timeout_timestamp() public { uint64 packetSeq = 1; IbcPacket memory pkt = IbcPacket(src, dest, packetSeq, payload, ZERO_HEIGHT, 1); - vm.expectEmit(true, true, true, true, address(dispatcher)); + vm.expectEmit(true, true, true, true, address(dispatcherProxy)); emit RecvPacket(address(mars), channelId, packetSeq); - vm.expectEmit(true, true, false, true, address(dispatcher)); + vm.expectEmit(true, true, false, true, address(dispatcherProxy)); emit WriteTimeoutPacket(address(mars), channelId, packetSeq, pkt.timeoutHeight, pkt.timeoutTimestamp); - dispatcher.recvPacket(IbcReceiver(mars), pkt, validProof); + dispatcherProxy.recvPacket(IbcReceiver(mars), pkt, validProof); } // recvPacket emits a WriteTimeoutPacket if block height passes chain B's block height function test_timeout_blockHeight() public { uint64 packetSeq = 1; IbcPacket memory pkt = IbcPacket(src, dest, packetSeq, payload, Height(0, 1), 0); - vm.expectEmit(true, true, true, true, address(dispatcher)); + vm.expectEmit(true, true, true, true, address(dispatcherProxy)); emit RecvPacket(address(mars), channelId, packetSeq); - vm.expectEmit(true, true, false, true, address(dispatcher)); + vm.expectEmit(true, true, false, true, address(dispatcherProxy)); emit WriteTimeoutPacket(address(mars), channelId, packetSeq, pkt.timeoutHeight, pkt.timeoutTimestamp); - dispatcher.recvPacket(IbcReceiver(mars), pkt, validProof); + dispatcherProxy.recvPacket(IbcReceiver(mars), pkt, validProof); } // cannot receive packets out of order for ordered channel function test_outOfOrder() public { - dispatcher.recvPacket(IbcReceiver(mars), IbcPacket(src, dest, 1, payload, ZERO_HEIGHT, maxTimeout), validProof); + dispatcherProxy.recvPacket( + IbcReceiver(mars), IbcPacket(src, dest, 1, payload, ZERO_HEIGHT, maxTimeout), validProof + ); vm.expectRevert(abi.encodeWithSignature("unexpectedPacketSequence()")); - dispatcher.recvPacket(IbcReceiver(mars), IbcPacket(src, dest, 3, payload, ZERO_HEIGHT, maxTimeout), validProof); + dispatcherProxy.recvPacket( + IbcReceiver(mars), IbcPacket(src, dest, 3, payload, ZERO_HEIGHT, maxTimeout), validProof + ); } // TODO: add tests for unordered channel, wrong port, and invalid proof } // Test Chain A receives an acknowledgement packet from Chain B -contract DispatcherAckPacketTest is PacketSenderTestBase { +contract DispatcherAckPacketTestSuite is PacketSenderTestBase { function test_success() public { for (uint64 index = 0; index < 3; index++) { sendPacket(); - vm.expectEmit(true, true, false, true, address(dispatcher)); + vm.expectEmit(true, true, false, true, address(dispatcherProxy)); emit Acknowledgement(address(mars), channelId, sentPacket.sequence); - dispatcher.acknowledgement(IbcReceiver(mars), sentPacket, ackPacket, validProof); + dispatcherProxy.acknowledgement(IbcReceiver(mars), sentPacket, ackPacket, validProof); // confirm dapp recieved the ack (bool success, bytes memory data) = mars.ackPackets(sentPacket.sequence - 1); AckPacket memory parsed = Ibc.parseAckData(ackPacket); @@ -396,14 +403,14 @@ contract DispatcherAckPacketTest is PacketSenderTestBase { // cannot ack packets if packet commitment is missing function test_missingPacket() public { vm.expectRevert(abi.encodeWithSignature("packetCommitmentNotFound()")); - dispatcher.acknowledgement(IbcReceiver(mars), genPacket(1), genAckPacket("1"), validProof); + dispatcherProxy.acknowledgement(IbcReceiver(mars), genPacket(1), genAckPacket("1"), validProof); sendPacket(); - dispatcher.acknowledgement(IbcReceiver(mars), sentPacket, ackPacket, validProof); + dispatcherProxy.acknowledgement(IbcReceiver(mars), sentPacket, ackPacket, validProof); // packet commitment is removed after ack vm.expectRevert(abi.encodeWithSignature("packetCommitmentNotFound()")); - dispatcher.acknowledgement(IbcReceiver(mars), sentPacket, ackPacket, validProof); + dispatcherProxy.acknowledgement(IbcReceiver(mars), sentPacket, ackPacket, validProof); } // cannot recieve ack packets out of order for ordered channel @@ -412,16 +419,16 @@ contract DispatcherAckPacketTest is PacketSenderTestBase { sendPacket(); } // 1st ack is ok - dispatcher.acknowledgement(IbcReceiver(mars), genPacket(1), genAckPacket("1"), validProof); + dispatcherProxy.acknowledgement(IbcReceiver(mars), genPacket(1), genAckPacket("1"), validProof); // only 2nd ack is allowed; so the 3rd ack fails vm.expectRevert(abi.encodeWithSignature("unexpectedPacketSequence()")); - dispatcher.acknowledgement(IbcReceiver(mars), genPacket(3), genAckPacket("3"), validProof); + dispatcherProxy.acknowledgement(IbcReceiver(mars), genPacket(3), genAckPacket("3"), validProof); } function test_invalidPort() public { - Mars earth = new Mars(dispatcher); + Mars earth = new Mars(dispatcherProxy); string memory earthPort = string(abi.encodePacked(portPrefix, getHexBytes(address(earth)))); IbcEndpoint memory earthEnd = IbcEndpoint(earthPort, channelId); @@ -432,7 +439,7 @@ contract DispatcherAckPacketTest is PacketSenderTestBase { packetEarth.src = earthEnd; vm.expectRevert(abi.encodeWithSignature("receiverNotOriginPacketSender()")); - dispatcher.acknowledgement(IbcReceiver(mars), packetEarth, ackPacket, validProof); + dispatcherProxy.acknowledgement(IbcReceiver(mars), packetEarth, ackPacket, validProof); } // ackPacket fails if channel doesn't match @@ -444,12 +451,12 @@ contract DispatcherAckPacketTest is PacketSenderTestBase { packet.src = invalidSrc; vm.expectRevert(abi.encodeWithSignature("packetCommitmentNotFound()")); - dispatcher.acknowledgement(IbcReceiver(mars), packet, ackPacket, validProof); + dispatcherProxy.acknowledgement(IbcReceiver(mars), packet, ackPacket, validProof); } } // Test Chain A receives a timeout packet from Chain B -contract DispatcherTimeoutPacketTest is PacketSenderTestBase { +contract DispatcherTimeoutPacketTestSuite is PacketSenderTestBase { // preconditions for timeout packet // - packet commitment exists // - packet timeout is verified by Polymer client @@ -457,9 +464,9 @@ contract DispatcherTimeoutPacketTest is PacketSenderTestBase { for (uint64 index = 0; index < 3; index++) { sendPacket(); - vm.expectEmit(true, true, true, true, address(dispatcher)); + vm.expectEmit(true, true, true, true, address(dispatcherProxy)); emit Timeout(address(mars), channelId, sentPacket.sequence); - dispatcher.timeout(IbcReceiver(mars), sentPacket, validProof); + dispatcherProxy.timeout(IbcReceiver(mars), sentPacket, validProof); } } @@ -467,29 +474,29 @@ contract DispatcherTimeoutPacketTest is PacketSenderTestBase { sentPacket = IbcPacket(srcRevertingMars, dest, 1, payload, ZERO_HEIGHT, maxTimeout); revertingBytesMars.greet(payloadStr, channelId, maxTimeout); nextSendSeq += 1; - vm.expectEmit(true, true, true, true, address(dispatcher)); + vm.expectEmit(true, true, true, true, address(dispatcherProxy)); emit TimeoutError( address(revertingBytesMars), abi.encodeWithSelector(RevertingBytesMars.OnTimeoutPacket.selector) ); - dispatcher.timeout(IbcReceiver(revertingBytesMars), sentPacket, validProof); + dispatcherProxy.timeout(IbcReceiver(revertingBytesMars), sentPacket, validProof); } // cannot timeout packets if packet commitment is missing function test_missingPacket() public { vm.expectRevert(abi.encodeWithSignature("packetCommitmentNotFound()")); - dispatcher.timeout(IbcReceiver(mars), genPacket(1), validProof); + dispatcherProxy.timeout(IbcReceiver(mars), genPacket(1), validProof); sendPacket(); - dispatcher.timeout(IbcReceiver(mars), sentPacket, validProof); + dispatcherProxy.timeout(IbcReceiver(mars), sentPacket, validProof); // packet commitment is removed after timeout vm.expectRevert(abi.encodeWithSignature("packetCommitmentNotFound()")); - dispatcher.timeout(IbcReceiver(mars), sentPacket, validProof); + dispatcherProxy.timeout(IbcReceiver(mars), sentPacket, validProof); } // cannot timeout packets if original packet port doesn't match current port function test_invalidPort() public { - Mars earth = new Mars(dispatcher); + Mars earth = new Mars(dispatcherProxy); string memory earthPort = string(abi.encodePacked(portPrefix, getHexBytes(address(earth)))); IbcEndpoint memory earthEnd = IbcEndpoint(earthPort, channelId); @@ -500,7 +507,7 @@ contract DispatcherTimeoutPacketTest is PacketSenderTestBase { packetEarth.src = earthEnd; vm.expectRevert(IBCErrors.receiverNotIntendedPacketDestination.selector); - dispatcher.timeout(IbcReceiver(mars), packetEarth, validProof); + dispatcherProxy.timeout(IbcReceiver(mars), packetEarth, validProof); } // cannot timeout packetsfails if channel doesn't match @@ -513,14 +520,14 @@ contract DispatcherTimeoutPacketTest is PacketSenderTestBase { vm.expectRevert(abi.encodeWithSignature("packetCommitmentNotFound()")); /* vm.expectRevert('Packet commitment not found'); */ - dispatcher.timeout(IbcReceiver(mars), packet, validProof); + dispatcherProxy.timeout(IbcReceiver(mars), packet, validProof); } // cannot timeout packets if proof from Polymer is invalid function test_invalidProof() public { sendPacket(); - vm.expectRevert(DummyConsensusStateManager.InvalidDummyNonMembershipProof.selector); - dispatcher.timeout(IbcReceiver(mars), sentPacket, invalidProof); + vm.expectRevert(DummyLightClient.InvalidDummyNonMembershipProof.selector); + dispatcherProxy.timeout(IbcReceiver(mars), sentPacket, invalidProof); } } @@ -537,18 +544,19 @@ contract DappRevertTests is Base { CounterParty("polyibc.eth2.71C95911E9a5D330f4D621842EC243EE1343292e", IbcUtils.toBytes32("channel-1"), "1.0"); function setUp() public override { - dispatcher = new Dispatcher(portPrefix, dummyConsStateManager); - revertingBytesMars = new RevertingBytesMars(dispatcher); - panickingMars = new PanickingMars(dispatcher); - revertingEmptyMars = new RevertingEmptyMars(dispatcher); - revertingStringMars = new RevertingStringMars(dispatcher); + (dispatcherProxy, dispatcherImplementation) = + TestUtilsTest.deployDispatcherProxyAndImpl(portPrefix, dummyConsStateManager); + revertingBytesMars = new RevertingBytesMars(dispatcherProxy); + panickingMars = new PanickingMars(dispatcherProxy); + revertingEmptyMars = new RevertingEmptyMars(dispatcherProxy); + revertingStringMars = new RevertingStringMars(dispatcherProxy); } function test_ibc_channel_open_non_dapp_call() public { address nonDappAddr = vm.addr(1); emit ChannelOpenInitError(nonDappAddr, bytes("call to non-contract")); - dispatcher.channelOpenInit( + dispatcherProxy.channelOpenInit( IbcChannelReceiver(nonDappAddr), ch1.version, ChannelOrder.NONE, false, connectionHops1, ch0.portId ); } @@ -556,7 +564,7 @@ contract DappRevertTests is Base { function test_ibc_channel_open_dapp_without_handler() public { Earth earth = new Earth(vm.addr(1)); emit ChannelOpenInitError(address(earth), ""); - dispatcher.channelOpenInit( + dispatcherProxy.channelOpenInit( IbcChannelReceiver(address(earth)), ch1.version, ChannelOrder.NONE, false, connectionHops1, ch0.portId ); } @@ -580,7 +588,7 @@ contract DappRevertTests is Base { packet.sequence, AckPacket(false, abi.encodeWithSelector(RevertingBytesMars.OnRecvPacketRevert.selector)) ); - dispatcher.recvPacket(revertingBytesMars, packet, validProof); + dispatcherProxy.recvPacket(revertingBytesMars, packet, validProof); // Test Revert String packet.dest.portId = string(abi.encodePacked(portPrefix, IbcUtils.toHexStr(address(revertingStringMars)))); @@ -591,13 +599,13 @@ contract DappRevertTests is Base { packet.sequence, AckPacket(false, abi.encodeWithSignature("Error(string)", "on recv packet is reverting")) ); - dispatcher.recvPacket(revertingStringMars, packet, validProof); + dispatcherProxy.recvPacket(revertingStringMars, packet, validProof); // Test Revert empty packet.dest.portId = string(abi.encodePacked(portPrefix, IbcUtils.toHexStr(address(revertingEmptyMars)))); vm.expectEmit(true, true, true, true); emit WriteAckPacket(address(revertingEmptyMars), packet.dest.channelId, packet.sequence, AckPacket(false, "")); - dispatcher.recvPacket(revertingEmptyMars, packet, validProof); + dispatcherProxy.recvPacket(revertingEmptyMars, packet, validProof); // Test Panic packet.dest.portId = string(abi.encodePacked(portPrefix, IbcUtils.toHexStr(address(panickingMars)))); @@ -608,16 +616,17 @@ contract DappRevertTests is Base { packet.sequence, AckPacket(false, abi.encodeWithSignature("Panic(uint256)", uint256(1))) ); - dispatcher.recvPacket(panickingMars, packet, validProof); + dispatcherProxy.recvPacket(panickingMars, packet, validProof); } function test_ack_packet_dapp_revert() public { // plant a fake packet commitment so the ack checks go through // use "forge inspect --storage" to find the slot - bytes32 slot1 = keccak256(abi.encode(address(revertingStringMars), uint32(7))); // current nested mapping slot: + bytes32 slot1 = keccak256(abi.encode(address(revertingStringMars), uint32(107))); // current nested mapping + // slot: bytes32 slot2 = keccak256(abi.encode(ch0.channelId, slot1)); bytes32 slot3 = keccak256(abi.encode(uint256(1), slot2)); - vm.store(address(dispatcher), slot3, bytes32(uint256(1))); + vm.store(address(dispatcherProxy), slot3, bytes32(uint256(1))); IbcPacket memory packet; packet.data = bytes("packet-1"); @@ -637,7 +646,7 @@ contract DappRevertTests is Base { address(revertingStringMars), abi.encodeWithSignature("Error(string)", "acknowledgement packet is reverting") ); - dispatcher.acknowledgement(revertingStringMars, packet, ack, validProof); + dispatcherProxy.acknowledgement(revertingStringMars, packet, ack, validProof); } function test_ibc_channel_open_dapp_revert() public { @@ -645,7 +654,7 @@ contract DappRevertTests is Base { emit ChannelOpenInitError( address(revertingStringMars), abi.encodeWithSignature("Error(string)", "open ibc channel is reverting") ); - dispatcher.channelOpenInit( + dispatcherProxy.channelOpenInit( revertingStringMars, ch1.version, ChannelOrder.NONE, false, connectionHops1, ch0.portId ); } @@ -655,6 +664,8 @@ contract DappRevertTests is Base { emit ChannelOpenAckError( address(revertingStringMars), abi.encodeWithSignature("Error(string)", "connect ibc channel is reverting") ); - dispatcher.channelOpenAck(revertingStringMars, ch0, connectionHops0, ChannelOrder.NONE, false, ch1, validProof); + dispatcherProxy.channelOpenAck( + revertingStringMars, ch0, connectionHops0, ChannelOrder.NONE, false, ch1, validProof + ); } } diff --git a/test/OpConsensusStateManager.t.sol b/test/OpConsensusStateManager.t.sol index 9fa33bbd..f9197322 100644 --- a/test/OpConsensusStateManager.t.sol +++ b/test/OpConsensusStateManager.t.sol @@ -2,12 +2,12 @@ pragma solidity ^0.8.0; import "forge-std/Test.sol"; -import "../contracts/core/OpConsensusStateManager.sol"; +import "../contracts/core/OpLightClient.sol"; import "../contracts/utils/DummyProofVerifier.sol"; import "./Proof.base.t.sol"; -contract OptimisticConsensusStateManagerTest is ProofBase { - OptimisticConsensusStateManager manager; +contract OptimisticLightClientTest is ProofBase { + OptimisticLightClient manager; ProofVerifier verifier; constructor() { @@ -16,7 +16,7 @@ contract OptimisticConsensusStateManagerTest is ProofBase { function setUp() public override { super.setUp(); - manager = new OptimisticConsensusStateManager(1, verifier, l1BlockProvider); + manager = new OptimisticLightClient(1, verifier, l1BlockProvider); } function test_addOpConsensusState_newOpConsensusStateCreatedWithPendingStatus() public { @@ -40,7 +40,7 @@ contract OptimisticConsensusStateManagerTest is ProofBase { function test_addOpConsensusState_addingPendingOpConsensusStateWithDifferentValuesIsError() public { manager.addOpConsensusState(emptyl1header, invalidStateProof, 1, 1); - vm.expectRevert(OptimisticConsensusStateManager.CannotUpdatePendingOptimisticConsensusState.selector); + vm.expectRevert(OptimisticLightClient.CannotUpdatePendingOptimisticConsensusState.selector); manager.addOpConsensusState(emptyl1header, invalidStateProof, 1, 2); } @@ -59,7 +59,7 @@ contract OptimisticConsensusStateManagerTest is ProofBase { } function test_zero_proof_window() public { - manager = new OptimisticConsensusStateManager(0, verifier, l1BlockProvider); + manager = new OptimisticLightClient(0, verifier, l1BlockProvider); manager.addOpConsensusState(emptyl1header, invalidStateProof, 1, 1); (,, bool ended) = manager.getState(1); assertEq(true, ended); @@ -72,12 +72,12 @@ contract OptimisticConsensusStateManagerTest is ProofBase { } } -contract OptimisticConsensusStateManagerWithRealVerifierTest is ProofBase { - OptimisticConsensusStateManager manager; +contract OptimisticLightClientWithRealVerifierTest is ProofBase { + OptimisticLightClient manager; function setUp() public override { super.setUp(); - manager = new OptimisticConsensusStateManager(1, opProofVerifier, l1BlockProvider); + manager = new OptimisticLightClient(1, opProofVerifier, l1BlockProvider); } function test_addOpConsensusState_newAppHashWithValidProof() public { diff --git a/test/TestUtils.t.sol b/test/TestUtils.t.sol new file mode 100644 index 00000000..9c6055ed --- /dev/null +++ b/test/TestUtils.t.sol @@ -0,0 +1,35 @@ +import {IDispatcher} from "../contracts/interfaces/IDispatcher.sol"; +import {LightClient} from "../contracts/interfaces/LightClient.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {Dispatcher} from "../contracts/core/Dispatcher.sol"; +// import {StdCheats} from "../forge-std/StdCheats.sol"; +import {Test} from "forge-std/Test.sol"; +import {Vm} from "forge-std/Vm.sol"; +// import {StdCheats} from "forge-std/StdCheats.sol"; + +pragma solidity ^0.8.0; + +abstract contract TestUtilsTest { + bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143; + bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + + function deployDispatcherProxyAndImpl(string memory initPortPrefix, LightClient lightClient) + public + returns (IDispatcher proxy, Dispatcher dispatcherImplementation) + { + dispatcherImplementation = new Dispatcher(); + proxy = IDispatcher( + address( + new ERC1967Proxy( + address(dispatcherImplementation), + abi.encodeWithSelector(Dispatcher.initialize.selector, initPortPrefix, lightClient) + ) + ) + ); + } + + function getProxyImplementation(address proxy, Vm vm) public view returns (address dispatcherImplementation) { + dispatcherImplementation = address(uint160(uint256(vm.load(address(proxy), _IMPLEMENTATION_SLOT)))); + } +} diff --git a/test/VirtualChain.sol b/test/VirtualChain.sol index d23e6f89..4b3802e7 100644 --- a/test/VirtualChain.sol +++ b/test/VirtualChain.sol @@ -3,6 +3,8 @@ pragma solidity ^0.8.13; import "forge-std/Test.sol"; import "@openzeppelin/contracts/utils/Strings.sol"; +import {IbcDispatcher, IbcEventsEmitter} from "../contracts/interfaces/IbcDispatcher.sol"; +import {IDispatcher} from "../contracts/interfaces/IDispatcher.sol"; import "../contracts/libs/Ibc.sol"; import "../contracts/core/Dispatcher.sol"; import "../contracts/interfaces/ProofVerifier.sol"; @@ -11,7 +13,8 @@ import {Mars} from "../contracts/examples/Mars.sol"; import {Earth} from "../contracts/examples/Earth.sol"; import {IbcMiddleware} from "../contracts/interfaces/IbcMiddleware.sol"; import {GeneralMiddleware} from "../contracts/base/GeneralMiddleware.sol"; -import "../contracts/utils/DummyConsensusStateManager.sol"; +import "../contracts/utils/DummyLightClient.sol"; +import {TestUtilsTest} from "./TestUtils.t.sol"; struct ChannelSetting { ChannelOrder ordering; @@ -23,7 +26,7 @@ struct ChannelSetting { } struct VirtualChainData { - Dispatcher dispatcher; + IDispatcher dispatcherProxy; UniversalChannelHandler ucHandler; Mars mars; Earth earth; @@ -33,8 +36,9 @@ struct VirtualChainData { } // A test contract that keeps two types of dApps, 1. regular IBC-enabled dApp, 2. universal channel dApp -contract VirtualChain is Test, IbcEventsEmitter { - Dispatcher public dispatcher; +contract VirtualChain is Test, IbcEventsEmitter, TestUtilsTest { + IDispatcher public dispatcherProxy; + Dispatcher public dispatcherImplementation; UniversalChannelHandler public ucHandler; GeneralMiddleware public mw1; GeneralMiddleware public mw2; @@ -54,10 +58,11 @@ contract VirtualChain is Test, IbcEventsEmitter { // ChannelIds are not initialized until channel handshake is started constructor(uint256 seed, string memory portPrefix) { _seed = seed; - dispatcher = new Dispatcher(portPrefix, new DummyConsensusStateManager()); - ucHandler = new UniversalChannelHandler(dispatcher); - mars = new Mars(dispatcher); + (dispatcherProxy, dispatcherImplementation) = deployDispatcherProxyAndImpl(portPrefix, new DummyLightClient()); + ucHandler = new UniversalChannelHandler(dispatcherProxy); + + mars = new Mars(dispatcherProxy); earth = new Earth(address(ucHandler)); // initialize portIds for counterparty chains address[3] memory portContracts = [address(ucHandler), address(mars), address(earth)]; @@ -74,7 +79,7 @@ contract VirtualChain is Test, IbcEventsEmitter { // return virtualChainData function getVirtualChainData() external view returns (VirtualChainData memory) { - return VirtualChainData(dispatcher, ucHandler, mars, earth, mw1, mw2, connectionHops); + return VirtualChainData(dispatcherProxy, ucHandler, mars, earth, mw1, mw2, connectionHops); } // expectedChannel returns a Channel struct with expected values @@ -148,7 +153,7 @@ contract VirtualChain is Test, IbcEventsEmitter { string memory cpPortId = remoteChain.portIds(address(remoteEnd)); require(bytes(cpPortId).length > 0, "channelOpenTry: portId does not exist"); - // set dispatcher's msg.sender to this function's msg.sender + // set dispatcherProxy's msg.sender to this function's msg.sender vm.prank(msg.sender); if (expPass) { @@ -162,7 +167,7 @@ contract VirtualChain is Test, IbcEventsEmitter { remoteChain.portIds(address(remoteEnd)) ); } - dispatcher.channelOpenInit( + dispatcherProxy.channelOpenInit( localEnd, setting.version, setting.ordering, setting.feeEnabled, connectionHops, cpPortId ); } @@ -180,7 +185,7 @@ contract VirtualChain is Test, IbcEventsEmitter { string memory cpPortId = remoteChain.portIds(address(remoteEnd)); require(bytes(cpPortId).length > 0, "channelOpenTry: portId does not exist"); - // set dispatcher's msg.sender to this function's msg.sender + // set dispatcherProxy's msg.sender to this function's msg.sender vm.prank(msg.sender); if (expPass) { @@ -195,7 +200,7 @@ contract VirtualChain is Test, IbcEventsEmitter { cpChanId ); } - dispatcher.channelOpenTry( + dispatcherProxy.channelOpenTry( localEnd, CounterParty(setting.portId, setting.channelId, setting.version), setting.ordering, @@ -223,14 +228,14 @@ contract VirtualChain is Test, IbcEventsEmitter { string memory cpPortId = remoteChain.portIds(address(remoteEnd)); require(bytes(cpPortId).length > 0, "channelOpenAck: counterparty portId does not exist"); - // set dispatcher's msg.sender to this function's msg.sender + // set dispatcherProxy's msg.sender to this function's msg.sender vm.prank(msg.sender); if (expPass) { vm.expectEmit(true, true, true, true); emit ChannelOpenAck(address(localEnd), chanId); } - dispatcher.channelOpenAck( + dispatcherProxy.channelOpenAck( localEnd, CounterParty(setting.portId, chanId, setting.version), connectionHops, @@ -265,7 +270,7 @@ contract VirtualChain is Test, IbcEventsEmitter { vm.expectEmit(true, true, true, true); emit ChannelOpenConfirm(address(localEnd), chanId); } - dispatcher.channelOpenConfirm( + dispatcherProxy.channelOpenConfirm( localEnd, CounterParty(setting.portId, chanId, setting.version), connectionHops, diff --git a/test/universal.channel.t.sol b/test/universal.channel.t.sol index 61296b91..3731b793 100644 --- a/test/universal.channel.t.sol +++ b/test/universal.channel.t.sol @@ -7,7 +7,7 @@ import {IbcEventsEmitter} from "../contracts/interfaces/IbcDispatcher.sol"; import {IbcReceiver} from "../contracts/interfaces/IbcReceiver.sol"; import "../contracts/core/UniversalChannelHandler.sol"; import "../contracts/examples/Mars.sol"; -import "../contracts/core/OpConsensusStateManager.sol"; +import "../contracts/core/OpLightClient.sol"; import "./Dispatcher.base.t.sol"; import "./VirtualChain.sol"; @@ -41,8 +41,8 @@ contract UniversalChannelTest is Base { assertEq(vc1.ucHandler().connectedChannels(0), channelId1); assertEq(vc2.ucHandler().connectedChannels(0), channelId2); - Channel memory channel1 = vc1.dispatcher().getChannel(address(vc1.ucHandler()), channelId1); - Channel memory channel2 = vc2.dispatcher().getChannel(address(vc2.ucHandler()), channelId2); + Channel memory channel1 = vc1.dispatcherProxy().getChannel(address(vc1.ucHandler()), channelId1); + Channel memory channel2 = vc2.dispatcherProxy().getChannel(address(vc2.ucHandler()), channelId2); Channel memory channel2Expected = vc1.expectedChannel(address(vc1.ucHandler()), channelId1, vc2.getConnectionHops(), setting); Channel memory channel1Expected = @@ -260,7 +260,7 @@ contract UniversalChannelPacketTest is Base, IbcMwEventsEmitter { emit SendPacket(address(v1.ucHandler), channelId1, packetData, packetSeq, timeout); v1.earth.greet(address(v2.earth), channelId1, appData, timeout); - // simulate relayer calling dispatcher.recvPacket on chain B + // simulate relayer calling dispatcherProxy.recvPacket on chain B // recvPacket is an IBC packet recvPacket = IbcPacket( IbcEndpoint(eth1.portIds(address(v1.ucHandler)), channelId1), @@ -272,7 +272,7 @@ contract UniversalChannelPacketTest is Base, IbcMwEventsEmitter { ); // - // simulate relayer calling dispatcher.timeout on chain A + // simulate relayer calling dispatcherProxy.timeout on chain A // // iterate over sending middleware contracts to verify each MW has witnessed the ack @@ -295,7 +295,7 @@ contract UniversalChannelPacketTest is Base, IbcMwEventsEmitter { vm.expectEmit(true, true, true, true); emit Timeout(address(v1.ucHandler), channelId1, packetSeq); // receive ack on chain A, triggering expected events - v1.dispatcher.timeout(v1.ucHandler, recvPacket, validProof); + v1.dispatcherProxy.timeout(v1.ucHandler, recvPacket, validProof); // verify timeout packet received by Earth on chain A (gotChannelId, gotUcPacket) = v1.earth.timeoutPackets(packetSeq - 1); @@ -346,7 +346,7 @@ contract UniversalChannelPacketTest is Base, IbcMwEventsEmitter { emit SendPacket(address(v1.ucHandler), channelId1, packetData, packetSeq, timeout); v1.earth.greet(address(v2.earth), channelId1, appData, timeout); - // simulate relayer calling dispatcher.recvPacket on chain B + // simulate relayer calling dispatcherProxy.recvPacket on chain B // recvPacket is an IBC packet recvPacket = IbcPacket( IbcEndpoint(eth1.portIds(address(v1.ucHandler)), channelId1), @@ -378,7 +378,7 @@ contract UniversalChannelPacketTest is Base, IbcMwEventsEmitter { // verify event emitted by Dispatcher vm.expectEmit(true, true, true, true); emit WriteAckPacket(address(v2.ucHandler), channelId2, packetSeq, ackPacket); - v2.dispatcher.recvPacket(v2.ucHandler, recvPacket, validProof); + v2.dispatcherProxy.recvPacket(v2.ucHandler, recvPacket, validProof); // verify packet received by Earth on chain B (gotChannelId, gotUcPacket) = v2.earth.recvedPackets(packetSeq - 1); @@ -386,7 +386,7 @@ contract UniversalChannelPacketTest is Base, IbcMwEventsEmitter { assertEq(abi.encode(gotUcPacket), abi.encode(ucPacket)); // - // simulate relayer calling dispatcher.acknowledgePacket on chain A + // simulate relayer calling dispatcherProxy.acknowledgePacket on chain A // // iterate over sending middleware contracts to verify each MW has witnessed the ack @@ -410,7 +410,7 @@ contract UniversalChannelPacketTest is Base, IbcMwEventsEmitter { vm.expectEmit(true, true, true, true); emit Acknowledgement(address(v1.ucHandler), channelId1, packetSeq); // receive ack on chain A, triggering expected events - v1.dispatcher.acknowledgement(v1.ucHandler, recvPacket, ackToBytes(ackPacket), validProof); + v1.dispatcherProxy.acknowledgement(v1.ucHandler, recvPacket, ackToBytes(ackPacket), validProof); // verify ack packet received by Earth on chain A (gotChannelId, gotUcPacket, gotAckPacket) = v1.earth.ackPackets(packetSeq - 1); diff --git a/test/upgradeableProxy/Dispatcher.upgrade.t.sol b/test/upgradeableProxy/Dispatcher.upgrade.t.sol new file mode 100644 index 00000000..dfe9df67 --- /dev/null +++ b/test/upgradeableProxy/Dispatcher.upgrade.t.sol @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/console2.sol"; +import {DispatcherUpdateClientTestSuite} from "../Dispatcher.client.t.sol"; +import {DispatcherIbcWithRealProofsSuite} from "../Dispatcher.proof.t.sol"; +import {Mars} from "../../contracts/examples/Mars.sol"; +import "../../contracts/core/OpLightClient.sol"; +import {ChannelHandshakeTestSuite, ChannelHandshakeTest, ChannelHandshakeUtils} from "../Dispatcher.t.sol"; +import {LocalEnd} from "../Dispatcher.base.t.sol"; +import {Base, ChannelHandshakeSetting} from "../Dispatcher.base.t.sol"; +import { + CounterParty, ChannelOrder, IbcEndpoint, IbcPacket, AckPacket, Ibc, Height +} from "../../contracts/libs/Ibc.sol"; +import {IbcReceiver} from "../../contracts/interfaces/IbcReceiver.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +import {OptimisticLightClient} from "../../contracts/core/OpLightClient.sol"; +import {ProofVerifier} from "../../contracts/core/OpProofVerifier.sol"; +import {DummyLightClient} from "../../contracts/utils/DummyLightClient.sol"; + +import {IDispatcher} from "../../contracts/interfaces/IDispatcher.sol"; +import {DispatcherV2Initializable} from "./upgrades/DispatcherV2Initializable.sol"; +import {DispatcherV2} from "./upgrades/DispatcherV2.sol"; + +abstract contract UpgradeTestUtils { + function upgradeDispatcher(LightClient consensusStateManager, string memory portPrefix, address dispatcherProxy) + public + returns (DispatcherV2Initializable newDispatcherImplementation) + { + // Upgrade dispatcherProxy for tests + newDispatcherImplementation = new DispatcherV2Initializable(); + bytes memory initData = abi.encodeWithSignature("initialize(string,address)", portPrefix, consensusStateManager); + UUPSUpgradeable(dispatcherProxy).upgradeToAndCall(address(newDispatcherImplementation), initData); + } +} + +contract ChannelHandShakeUpgradeUtil is ChannelHandshakeUtils { + uint32 nextSequenceSendSlot = 104; + uint32 sendPacketCommitmentSlot = 107; + uint32 nextSequenceAckSlot = 106; + uint32 nextSequenceRecvSlot = 105; + IbcPacket[3] packets; + string payload = "msgPayload"; + bytes packet = abi.encodePacked(payload); + uint64 timeoutTimestamp = 1000; + + function doChannelHandshake() public { + // same setup as that run in test_connectChannel_ok + ChannelHandshakeSetting[8] memory settings = createSettings2(true); + + string[2] memory versions = ["1.0", "2.0"]; + for (uint256 i = 0; i < settings.length; i++) { + for (uint256 j = 0; j < versions.length; j++) { + LocalEnd memory le = _local; + CounterParty memory re = _remote; + le.versionCall = versions[j]; + le.versionExpected = versions[j]; + re.version = versions[j]; + channelOpenInit(le, re, settings[i], true); + channelOpenTry(le, re, settings[i], true); + channelOpenAck(le, re, settings[i], true); + channelOpenConfirm(le, re, settings[i], true); + } + } + } + + function sendOnePacket(bytes32 channelId, uint64 packetSeq) public { + vm.expectEmit(true, true, true, true); + emit SendPacket(address(mars), channelId, packet, packetSeq, timeoutTimestamp); + mars.greet(payload, channelId, timeoutTimestamp); + } + + function sendPacket(bytes32 channelId) public { + for (uint64 index = 0; index < 3; index++) { + uint64 packetSeq = index + 1; + sendOnePacket(channelId, packetSeq); + IbcEndpoint memory dest = IbcEndpoint("polyibc.bsc.9876543210", "channel-99"); + string memory marsPort = string(abi.encodePacked(portPrefix, getHexBytes(address(mars)))); + IbcEndpoint memory src = IbcEndpoint(marsPort, channelId); + packets[index] = IbcPacket(src, dest, packetSeq, bytes(payload), ZERO_HEIGHT, maxTimeout); + } + } + + function acknowledgePacket() public {} + + function findNextSequenceSendSlot(address portAddress, bytes32 channelId) public view returns (bytes32 slot) { + bytes32 slot1 = keccak256(abi.encode(portAddress, nextSequenceSendSlot)); + slot = keccak256(abi.encode(channelId, slot1)); + } + + function findPacketCommitmentSlot(address portAddress, bytes32 channelId, uint64 sequence) + public + view + returns (bytes32 slot) + { + bytes32 slot1 = keccak256(abi.encode(portAddress, sendPacketCommitmentSlot)); + bytes32 slot2 = keccak256(abi.encode(channelId, slot1)); + slot = keccak256(abi.encode(sequence, slot2)); + } + + function findNextSequenceAck(address portAddress, bytes32 channelId) public view returns (bytes32 slot) { + bytes32 slot1 = keccak256(abi.encode(portAddress, nextSequenceAckSlot)); + slot = keccak256(abi.encode(channelId, slot1)); + } + + function findNextSequenceRecv(address portAddress, bytes32 channelId) public view returns (bytes32 slot) { + bytes32 slot1 = keccak256(abi.encode(portAddress, nextSequenceRecvSlot)); + slot = keccak256(abi.encode(channelId, slot1)); + } +} + +contract DispatcherUpgradeTest is ChannelHandShakeUpgradeUtil, UpgradeTestUtils { + function setUp() public override { + (dispatcherProxy, dispatcherImplementation) = deployDispatcherProxyAndImpl(portPrefix, dummyConsStateManager); + mars = new Mars(dispatcherProxy); + _local = LocalEnd(mars, portId, "channel-1", connectionHops, "1.0", "1.0"); + _remote = CounterParty("eth2.7E5F4552091A69125d5DfCb7b8C2659029395Bdf", "channel-2", "1.0"); + + LightClient newLightClient = opLightClient; + // Add state to test if impacted by upgrade + doChannelHandshake(); + sendPacket(_local.channelId); + + // Upgrade dispatcherProxy for tests + upgradeDispatcher(newLightClient, "adfsafsa", address(dispatcherProxy)); + } + + function test_SentPacketState_Conserved() public { + // Check packet state in sendPacketCommitment()[] + uint64 nextSequenceSendValue = uint64( + uint256(vm.load(address(dispatcherProxy), findNextSequenceSendSlot(address(mars), _local.channelId))) + ); + + assertEq(4, nextSequenceSendValue); + + // packets + assert(vm.load(address(dispatcherProxy), findPacketCommitmentSlot(address(mars), _local.channelId, 1)) > 0); + assert(vm.load(address(dispatcherProxy), findPacketCommitmentSlot(address(mars), _local.channelId, 2)) > 0); + assert(vm.load(address(dispatcherProxy), findPacketCommitmentSlot(address(mars), _local.channelId, 3)) > 0); + + // test sending packet with the updated contract + sendOnePacket(_local.channelId, 4); + assert(vm.load(address(dispatcherProxy), findPacketCommitmentSlot(address(mars), _local.channelId, 4)) > 0); + uint64 nextSequenceSendAfterSending = uint64( + uint256(vm.load(address(dispatcherProxy), findNextSequenceSendSlot(address(mars), _local.channelId))) + ); + assertEq(5, nextSequenceSendAfterSending); + } + + function test_OpenChannelState_Conserved() public { + // State should be conserved after upgrade + uint64 nextSequenceRecvValue = + uint64(uint256(vm.load(address(dispatcherProxy), findNextSequenceRecv(address(mars), _local.channelId)))); + uint64 nextSequenceAckValue = + uint64(uint256(vm.load(address(dispatcherProxy), findNextSequenceAck(address(mars), _local.channelId)))); + + assertEq(1, nextSequenceRecvValue); + assertEq(1, nextSequenceAckValue); + } +} diff --git a/test/upgradeableProxy/DispatcherUUPS.accessControl.t.sol b/test/upgradeableProxy/DispatcherUUPS.accessControl.t.sol new file mode 100644 index 00000000..1825cb8a --- /dev/null +++ b/test/upgradeableProxy/DispatcherUUPS.accessControl.t.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "../../contracts/libs/Ibc.sol"; +import {Dispatcher} from "../../contracts/core/Dispatcher.sol"; +import {IbcEventsEmitter} from "../../contracts/interfaces/IbcDispatcher.sol"; +import {IbcReceiver} from "../../contracts/interfaces/IbcReceiver.sol"; +import "../../contracts/examples/Mars.sol"; +import "../../contracts/core/OpLightClient.sol"; +import "../Dispatcher.base.t.sol"; +import {DispatcherV2} from "./upgrades/DispatcherV2.sol"; +import {DispatcherV2Initializable} from "./upgrades/DispatcherV2Initializable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +import {DummyLightClient} from "../../contracts/utils/DummyLightClient.sol"; + +contract DispatcherUUPSAccessControl is Base { + string public portPrefix2 = "IIpolyibc.eth."; + LightClient lightClient2 = new DummyLightClient(); + address public notOwner = vm.addr(1); + DispatcherV2 dispatcherImplementation2; + DispatcherV2Initializable dispatcherImplementation3; + + function setUp() public override { + (dispatcherProxy, dispatcherImplementation) = deployDispatcherProxyAndImpl(portPrefix, dummyConsStateManager); + dispatcherImplementation2 = new DispatcherV2(); + dispatcherImplementation3 = new DispatcherV2Initializable(); + } + + function test_Dispatcher_Allows_Upgrade() public { + assertEq(address(dispatcherImplementation), getProxyImplementation(address(dispatcherProxy), vm)); + UUPSUpgradeable(address(dispatcherProxy)).upgradeTo(address(dispatcherImplementation2)); + assertEq(address(dispatcherImplementation2), getProxyImplementation(address(dispatcherProxy), vm)); + assertEq(dispatcherProxy.portPrefix(), portPrefix); + assertEq( + address(uint160(uint256(vm.load(address(dispatcherProxy), bytes32(uint256(110)))))), + address(dummyConsStateManager) + ); + } + + function test_Dispatcher_Allows_Upgrade_To_And_Call() public { + assertEq(address(dispatcherImplementation), getProxyImplementation(address(dispatcherProxy), vm)); + bytes memory initData = abi.encodeWithSignature("initialize(string,address)", portPrefix2, lightClient2); + UUPSUpgradeable(address(dispatcherProxy)).upgradeToAndCall(address(dispatcherImplementation3), initData); + assertEq(address(dispatcherImplementation3), getProxyImplementation(address(dispatcherProxy), vm)); + assertEq(dispatcherProxy.portPrefix(), portPrefix2); + assertEq( + address(uint160(uint256(vm.load(address(dispatcherProxy), bytes32(uint256(110)))))), address(lightClient2) + ); + } + + function test_Dispatcher_Prevents_Non_Owner_Updgrade() public { + vm.startPrank(notOwner); + vm.expectRevert("Ownable: caller is not the owner"); + UUPSUpgradeable(address(dispatcherProxy)).upgradeTo(address(dispatcherImplementation2)); + } + + function test_Dispatcher_Prevents_Non_Owner_UpdgradeToAndCall() public { + vm.expectRevert("Function must be called through delegatecall"); + UUPSUpgradeable(address(dispatcherImplementation)).upgradeTo(address(dispatcherImplementation2)); + } + + function test_Dispatcher_Prevents_Reinit_Attacks() public { + vm.expectRevert("Initializable: contract is already initialized"); + dispatcherImplementation.initialize("IIpolyibc.eth.", dummyConsStateManager); + } +} diff --git a/test/upgradeableProxy/upgrades/DispatcherV2.sol b/test/upgradeableProxy/upgrades/DispatcherV2.sol new file mode 100644 index 00000000..de7e7298 --- /dev/null +++ b/test/upgradeableProxy/upgrades/DispatcherV2.sol @@ -0,0 +1,613 @@ +//SPDX-License-Identifier: UNLICENSED + +pragma solidity ^0.8.9; + +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +import {OwnableUpgradeable} from "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import {IbcChannelReceiver, IbcPacketReceiver} from "../../../contracts/interfaces/IbcReceiver.sol"; +import {L1Header, OpL2StateProof, Ics23Proof} from "../../../contracts/interfaces/ProofVerifier.sol"; +import {LightClient} from "../../../contracts/interfaces/LightClient.sol"; +import {IDispatcher} from "../../../contracts/interfaces/IDispatcher.sol"; +import { + Channel, + CounterParty, + ChannelOrder, + IbcPacket, + ChannelState, + AckPacket, + IBCErrors, + IbcUtils, + Ibc +} from "../../../contracts/libs/Ibc.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; + +/** + * @title Dispatcher + * @author Polymer Labs + * @notice + * Contract callers call this contract to send IBC-like msg, + * which can be relayed to a rollup module on the Polymerase chain + */ +contract DispatcherV2 is OwnableUpgradeable, UUPSUpgradeable, IDispatcher { + // + // fields + // + // IBC_PortID = portPrefix + address (hex string without 0x prefix, case insensitive) + string public portPrefix; + uint32 public portPrefixLen; + + mapping(address => mapping(bytes32 => Channel)) public portChannelMap; + mapping(address => mapping(bytes32 => uint64)) public nextSequenceSend; + // keep track of received packets' sequences to ensure channel ordering is enforced for ordered channels + mapping(address => mapping(bytes32 => uint64)) public nextSequenceRecv; + mapping(address => mapping(bytes32 => uint64)) public nextSequenceAck; + // only stores a bit to mark packet has not been ack'ed or timed out yet; actual IBC packet verification is done on + // Polymer chain. + // Keep track of sent packets + mapping(address => mapping(bytes32 => mapping(uint64 => bool))) public sendPacketCommitment; + // keep track of received packets to prevent replay attack + mapping(address => mapping(bytes32 => mapping(uint64 => bool))) public recvPacketReceipt; + // keep track of outbound ack packets to prevent replay attack + mapping(address => mapping(bytes32 => mapping(uint64 => bool))) public ackPacketCommitment; + + LightClient public lightClient; + + // + // methods + // + constructor() { + _disableInitializers(); + } + + function initialize(string memory initPortPrefix, LightClient _lightClient) public virtual initializer { + __Ownable_init(); + portPrefix = initPortPrefix; + portPrefixLen = uint32(bytes(initPortPrefix).length); + lightClient = _lightClient; + } + + // + // CoreSC maaintainer methods, only invoked by the owner + // + function setPortPrefix(string calldata _portPrefix) external onlyOwner { + portPrefix = _portPrefix; + portPrefixLen = uint32(bytes(_portPrefix).length); + } + + // updateClientWithOptimisticConsensusState updates the client + // with the optimistic consensus state. The optimistic consensus + // is accepted and will be open for verify in the fraud proof + // window. + function updateClientWithOptimisticConsensusState( + L1Header calldata l1header, + OpL2StateProof calldata proof, + uint256 height, + uint256 appHash + ) external returns (uint256 fraudProofEndTime, bool ended) { + return lightClient.addOpConsensusState(l1header, proof, height, appHash); + } + + /** + * 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 receiver, + string calldata version, + ChannelOrder ordering, + bool feeEnabled, + string[] calldata connectionHops, + string calldata counterpartyPortId + ) external { + if (bytes(counterpartyPortId).length == 0) { + revert IBCErrors.invalidCounterPartyPortId(); + } + + (bool success, bytes memory data) = _callIfContract( + address(receiver), abi.encodeWithSelector(IbcChannelReceiver.onChanOpenInit.selector, version) + ); + + if (success) { + emit ChannelOpenInit( + address(receiver), abi.decode(data, (string)), ordering, feeEnabled, connectionHops, counterpartyPortId + ); + } else { + emit ChannelOpenInitError(address(receiver), data); + } + } + + /** + * 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 receiver, + CounterParty calldata local, + ChannelOrder ordering, + bool feeEnabled, + string[] calldata connectionHops, + CounterParty calldata counterparty, + Ics23Proof calldata proof + ) external { + if (bytes(counterparty.portId).length == 0) { + revert IBCErrors.invalidCounterPartyPortId(); + } + + lightClient.verifyMembership( + proof, + Ibc.channelProofKey(local.portId, local.channelId), + Ibc.channelProofValue(ChannelState.TRY_PENDING, ordering, local.version, connectionHops, counterparty) + ); + + (bool success, bytes memory data) = _callIfContract( + address(receiver), abi.encodeWithSelector(IbcChannelReceiver.onChanOpenTry.selector, counterparty.version) + ); + + if (success) { + emit ChannelOpenTry( + address(receiver), + abi.decode(data, (string)), + ordering, + feeEnabled, + connectionHops, + counterparty.portId, + counterparty.channelId + ); + } else { + emit ChannelOpenTryError(address(receiver), data); + } + } + + /** + * 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 receiver, + CounterParty calldata local, + string[] calldata connectionHops, + ChannelOrder ordering, + bool feeEnabled, + CounterParty calldata counterparty, + Ics23Proof calldata proof + ) external { + lightClient.verifyMembership( + proof, + Ibc.channelProofKey(local.portId, local.channelId), + Ibc.channelProofValue(ChannelState.ACK_PENDING, ordering, local.version, connectionHops, counterparty) + ); + + (bool success, bytes memory data) = _callIfContract( + address(receiver), + abi.encodeWithSelector(IbcChannelReceiver.onChanOpenAck.selector, local.channelId, counterparty.version) + ); + + if (success) { + _connectChannel(receiver, local, connectionHops, ordering, feeEnabled, counterparty); + emit ChannelOpenAck(address(receiver), local.channelId); + } else { + emit ChannelOpenAckError(address(receiver), data); + } + } + + /** + * 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 receiver, + CounterParty calldata local, + string[] calldata connectionHops, + ChannelOrder ordering, + bool feeEnabled, + CounterParty calldata counterparty, + Ics23Proof calldata proof + ) external { + lightClient.verifyMembership( + proof, + Ibc.channelProofKey(local.portId, local.channelId), + Ibc.channelProofValue(ChannelState.CONFIRM_PENDING, ordering, local.version, connectionHops, counterparty) + ); + + (bool success, bytes memory data) = _callIfContract( + address(receiver), + abi.encodeWithSelector(IbcChannelReceiver.onChanOpenConfirm.selector, local.channelId, counterparty.version) + ); + + if (success) { + _connectChannel(receiver, local, connectionHops, ordering, feeEnabled, counterparty); + emit ChannelOpenConfirm(address(receiver), local.channelId); + } else { + emit ChannelOpenConfirmError(address(receiver), data); + } + } + + /** + * @dev Emits a `CloseIbcChannel` event with the given `channelId` and the address of the message sender + * @notice Close the specified IBC channel by channel ID + * Must be called by the channel owner, ie. portChannelMap[msg.sender][channelId] must exist + */ + function closeIbcChannel(bytes32 channelId) external { + Channel memory channel = portChannelMap[msg.sender][channelId]; + if (channel.counterpartyChannelId == bytes32(0)) { + revert IBCErrors.channelNotOwnedBySender(); + } + + (bool success, bytes memory data) = _callIfContract( + msg.sender, + abi.encodeWithSelector( + IbcChannelReceiver.onCloseIbcChannel.selector, + channelId, + channel.counterpartyPortId, + channel.counterpartyChannelId + ) + ); + if (success) { + emit CloseIbcChannel(msg.sender, channelId); + } else { + emit CloseIbcChannelError(address(msg.sender), data); + } + } + + /** + * This func is called by a 'relayer' after the IBC/VIBC hub chain has processed ChanCloseConfirm event. + * The dApp's onCloseIbcChannel callback is invoked. + * dApp should throw an error if the channel should not be closed. + */ + // FIXME this is commented out to make the contract size smaller. We need to optimise for size + // function onCloseIbcChannel(address portAddress, bytes32 channelId, Ics23Proof calldata proof) external { + // // verify VIBC/IBC hub chain has processed ChanCloseConfirm event + // lightClient.verifyMembership( + // proof, + // bytes('channel/path/to/be/added/here'), + // bytes('expected channel bytes constructed from params. Channel.State = {Closed(_Pending?)}') + // ); + // + // // ensure port owns channel + // Channel memory channel = portChannelMap[portAddress][channelId]; + // if (channel.counterpartyChannelId == bytes32(0)) { + // revert channelNotOwnedByPortAddress(); + // } + // + // // confirm with dApp by calling its callback + // IbcChannelReceiver reciever = IbcChannelReceiver(portAddress); + // reciever.onCloseIbcChannel(channelId, channel.counterpartyPortId, channel.counterpartyChannelId); + // delete portChannelMap[portAddress][channelId]; + // emit CloseIbcChannel(portAddress, channelId); + // } + + // + // IBC Packet methods + // + + /** + * @notice Sends an IBC packet on a existing channel with the specified packet data and timeout block timestamp. + * @notice Data should be encoded in a format defined by the channel version, and the module on the other side + * should know how to parse this. + * @dev Emits an `IbcPacketEvent` event containing the sender address, channel ID, packet data, and timeout block + * timestamp. + * @param channelId The ID of the channel on which to send the packet. + * @param packet The packet data to send. + * @param timeoutTimestamp The timestamp in nanoseconds after which the packet times out if it has not been + * received. + */ + function sendPacket(bytes32 channelId, bytes calldata packet, uint64 timeoutTimestamp) external { + // ensure port owns channel + Channel memory channel = portChannelMap[msg.sender][channelId]; + if (channel.counterpartyChannelId == bytes32(0)) { + revert IBCErrors.channelNotOwnedBySender(); + } + + _sendPacket(msg.sender, channelId, packet, timeoutTimestamp); + } + /** + * @notice Handle the acknowledgement of an IBC packet by the counterparty + * @dev Verifies the given proof and calls the `onAcknowledgementPacket` function on the given `receiver` contract, + * ie. the IBC dApp. + * Prerequisite: the original packet is committed and not ack'ed or timed out yet. + * @param receiver The IbcPacketHandler contract that should handle the packet acknowledgement event + * If the address doesn't satisfy the interface, the transaction will be reverted. + * @param packet The IbcPacket data for the acknowledged packet + * @param ack The acknowledgement receipt for the packet + * @param proof The membership proof to verify the packet acknowledgement committed on Polymer chain + */ + + function acknowledgement( + IbcPacketReceiver receiver, + IbcPacket calldata packet, + bytes calldata ack, + Ics23Proof calldata proof + ) external { + // verify `receiver` is the original packet sender + if (!portIdAddressMatch(address(receiver), packet.src.portId)) { + revert IBCErrors.receiverNotOriginPacketSender(); + } + + // prove ack packet is on Polymer chain + lightClient.verifyMembership(proof, Ibc.ackProofKey(packet), abi.encode(Ibc.ackProofValue(ack))); + // verify packet has been committed and not yet ack'ed or timed out + bool hasCommitment = sendPacketCommitment[address(receiver)][packet.src.channelId][packet.sequence]; + if (!hasCommitment) { + revert IBCErrors.packetCommitmentNotFound(); + } + + // enforce ack'ed packet sequences always increment by 1 for ordered channels + Channel memory channel = portChannelMap[address(receiver)][packet.src.channelId]; + (bool success, bytes memory data) = _callIfContract( + address(receiver), + abi.encodeWithSelector(IbcPacketReceiver.onAcknowledgementPacket.selector, packet, Ibc.parseAckData(ack)) + ); + + if (success) { + if (channel.ordering == ChannelOrder.ORDERED) { + if (packet.sequence != nextSequenceAck[address(receiver)][packet.src.channelId]) { + revert IBCErrors.unexpectedPacketSequence(); + } + + nextSequenceAck[address(receiver)][packet.src.channelId] = packet.sequence + 1; + } + + // delete packet commitment to avoid double ack + delete sendPacketCommitment[address(receiver)][packet.src.channelId][packet.sequence]; + emit Acknowledgement(address(receiver), packet.src.channelId, packet.sequence); + } else { + emit AcknowledgementError(address(receiver), data); + } + } + + /** + * @notice Timeout of an IBC packet + * @dev Verifies the given proof and calls the `onTimeoutPacket` function on the given `receiver` contract, ie. the + * IBC-dApp. + * Prerequisite: the original packet is committed and not ack'ed or timed out yet. + * @param receiver The IbcPacketHandler contract that should handle the packet timeout event + * If the address doesn't satisfy the interface, the transaction will be reverted. + * @param packet The IbcPacket data for the timed-out packet + * @param proof The non-membership proof data needed to verify the packet timeout + */ + function timeout(IbcPacketReceiver receiver, IbcPacket calldata packet, Ics23Proof calldata proof) external { + // verify `receiver` is the original packet sender + if (!portIdAddressMatch(address(receiver), packet.src.portId)) { + revert IBCErrors.receiverNotIntendedPacketDestination(); + } + + // prove absence of packet receipt on Polymer chain + // TODO: add non membership support + lightClient.verifyNonMembership(proof, "packet/receipt/path"); + + // verify packet has been committed and not yet ack'ed or timed out + bool hasCommitment = sendPacketCommitment[address(receiver)][packet.src.channelId][packet.sequence]; + if (!hasCommitment) { + revert IBCErrors.packetCommitmentNotFound(); + } + + (bool success, bytes memory data) = _callIfContract( + address(receiver), abi.encodeWithSelector(IbcPacketReceiver.onTimeoutPacket.selector, packet) + ); + if (success) { + // delete packet commitment to avoid double timeout + delete sendPacketCommitment[address(receiver)][packet.src.channelId][packet.sequence]; + emit Timeout(address(receiver), packet.src.channelId, packet.sequence); + } else { + emit TimeoutError(address(receiver), data); + } + } + + /** + * @notice Receive an IBC packet and then pass it to the IBC-dApp for processing if verification succeeds. + * @dev Verifies the given proof and calls the `onRecvPacket` function on the given `receiver` contract + * @param receiver The IbcPacketHandler contract that should handle the packet receipt event + * If the address doesn't satisfy the interface, the transaction will be reverted. + * The receiver must be the intended packet destination, which is the same as packet.dest.portId. + * @param packet The IbcPacket data for the received packet + * @param proof The proof data needed to verify the packet receipt + * @dev Emit an `RecvPacket` event with the details of the received packet; + * Also emit a WriteAckPacket event, which can be relayed to Polymer chain by relayers + */ + function recvPacket(IbcPacketReceiver receiver, IbcPacket calldata packet, Ics23Proof calldata proof) external { + // verify `receiver` is the intended packet destination + if (!portIdAddressMatch(address(receiver), packet.dest.portId)) { + revert IBCErrors.receiverNotIntendedPacketDestination(); + } + lightClient.verifyMembership( + proof, Ibc.packetCommitmentProofKey(packet), abi.encode(Ibc.packetCommitmentProofValue(packet)) + ); + + // verify packet has not been received yet + bool hasReceipt = recvPacketReceipt[address(receiver)][packet.dest.channelId][packet.sequence]; + if (hasReceipt) { + revert IBCErrors.packetReceiptAlreadyExists(); + } + + recvPacketReceipt[address(receiver)][packet.dest.channelId][packet.sequence] = true; + + // enforce recv'ed packet sequences always increment by 1 for ordered channels + Channel memory channel = portChannelMap[address(receiver)][packet.dest.channelId]; + if (channel.ordering == ChannelOrder.ORDERED) { + if (packet.sequence != nextSequenceRecv[address(receiver)][packet.dest.channelId]) { + revert IBCErrors.unexpectedPacketSequence(); + } + + nextSequenceRecv[address(receiver)][packet.dest.channelId] = packet.sequence + 1; + } + + // Emit recv packet event to prove the relayer did the correct job, and pkt is received. + emit RecvPacket(address(receiver), packet.dest.channelId, packet.sequence); + + // If pkt is already timed out, then return early so dApps won't receive it. + if (_isPacketTimeout(packet)) { + address writerPortAddress = address(receiver); + emit WriteTimeoutPacket( + writerPortAddress, packet.dest.channelId, packet.sequence, packet.timeoutHeight, packet.timeoutTimestamp + ); + return; + } + + // Not timeout yet, then do normal handling + IbcPacket memory pkt = packet; + AckPacket memory ack; + (bool success, bytes memory data) = + _callIfContract(address(receiver), abi.encodeWithSelector(IbcPacketReceiver.onRecvPacket.selector, pkt)); + if (success) { + (ack) = abi.decode(data, (AckPacket)); + } else { + ack = AckPacket(false, data); + } + bool hasAckPacketCommitment = ackPacketCommitment[address(receiver)][packet.dest.channelId][packet.sequence]; + // check is not necessary for sync-acks + if (hasAckPacketCommitment) { + revert IBCErrors.ackPacketCommitmentAlreadyExists(); + } + + ackPacketCommitment[address(receiver)][packet.dest.channelId][packet.sequence] = true; + + emit WriteAckPacket(address(receiver), packet.dest.channelId, packet.sequence, ack); + } + + // TODO: add async writeAckPacket + // // this can be invoked sync or async by the IBC-dApp + // function writeAckPacket(IbcPacket calldata packet, AckPacket calldata ackPacket) external { + // // verify `receiver` is the original packet sender + // require( + // portIdAddressMatch(address(msg.sender), packet.src.portId), + // 'Receiver is not the original packet sender' + // ); + // } + + // TODO: remove below writeTimeoutPacket() function + // 1. core SC is responsible to generate timeout packet + // 2. user contract are not free to generate timeout with different criteria + // 3. [optional]: we may wish relayer to trigger timeout process, but in this case, below function won't do + // the job, as it doesn't have proofs. + // There is no strong reason to do this, as relayer can always do the regular `recvPacket` flow, which will + // do proper timeout generation. + /** + * Generate a timeout packet for the given packet + */ + function writeTimeoutPacket(address receiver, IbcPacket calldata packet) external { + // verify `receiver` is the original packet sender + if (!portIdAddressMatch(receiver, packet.src.portId)) { + revert IBCErrors.receiverNotIntendedPacketDestination(); + } + + // verify packet does not have a receipt + bool hasReceipt = recvPacketReceipt[receiver][packet.dest.channelId][packet.sequence]; + if (hasReceipt) { + revert IBCErrors.packetReceiptAlreadyExists(); + } + + // verify packet has timed out; zero-value in packet.timeout means no timeout set + if (!_isPacketTimeout(packet)) { + revert IBCErrors.packetNotTimedOut(); + } + + emit WriteTimeoutPacket( + receiver, packet.dest.channelId, packet.sequence, packet.timeoutHeight, packet.timeoutTimestamp + ); + } + + /** + * @notice Get the IBC channel with the specified port and channel ID + * @param portAddress EVM address of the IBC port + * @param channelId IBC channel ID from the port perspective + * @return channel A channel struct is always returned. If it doesn't exists, the channel struct is populated with + * default + * values per EVM. + */ + function getChannel(address portAddress, bytes32 channelId) external view returns (Channel memory channel) { + channel = portChannelMap[portAddress][channelId]; + } + + // getOptimisticConsensusState + function getOptimisticConsensusState(uint256 height) + external + view + returns (uint256 appHash, uint256 fraudProofEndTime, bool ended) + { + return lightClient.getState(height); + } + + // verify an EVM address matches an IBC portId. + // IBC_PortID = portPrefix + address (hex string without 0x prefix, case-insensitive) + function portIdAddressMatch(address addr, string calldata portId) public view returns (bool isMatch) { + if (keccak256(abi.encodePacked(portPrefix)) != keccak256(abi.encodePacked(portId[0:portPrefixLen]))) { + return false; + } + string memory portSuffix = portId[portPrefixLen:]; + isMatch = Ibc._hexStrToAddress(portSuffix) == addr; + } + + // Prerequisite: must verify sender is authorized to send packet on the channel + function _sendPacket(address sender, bytes32 channelId, bytes memory packet, uint64 timeoutTimestamp) internal { + // current packet sequence + uint64 sequence = nextSequenceSend[sender][channelId]; + if (sequence == 0) { + revert IBCErrors.invalidPacketSequence(); + } + + // packet commitment + sendPacketCommitment[sender][channelId][sequence] = true; + // increment nextSendPacketSequence + nextSequenceSend[sender][channelId] = sequence + 1; + + emit SendPacket(sender, channelId, packet, sequence, timeoutTimestamp); + } + + function _connectChannel( + IbcChannelReceiver portAddress, + CounterParty calldata local, + string[] calldata connectionHops, + ChannelOrder ordering, + bool feeEnabled, + CounterParty calldata counterparty + ) internal { + // Register port and channel mapping + // TODO: check duplicated channel registration? + // TODO: The call to `Channel` constructor MUST be move to `openIbcChannel` phase + // Then `connectIbcChannel` phase can use the `version` as part of `require` condition. + portChannelMap[address(portAddress)][local.channelId] = Channel( + counterparty.version, // TODO: this should be self version instead of counterparty version + ordering, + feeEnabled, + connectionHops, + counterparty.portId, + counterparty.channelId + ); + + // initialize channel sequences + nextSequenceSend[address(portAddress)][local.channelId] = 1; + nextSequenceRecv[address(portAddress)][local.channelId] = 1; + nextSequenceAck[address(portAddress)][local.channelId] = 1; + } + + // Returns the result of the call if no revert, otherwise returns the error if thrown. + function _callIfContract(address receiver, bytes memory args) + internal + returns (bool success, bytes memory message) + { + if (!Address.isContract(receiver)) { + return (false, bytes("call to non-contract")); + } + // Only call if we are sure receiver is a contract + // Note: This tx won't revert if the low-level call fails, see + // https://docs.soliditylang.org/en/latest/cheatsheet.html#members-of-address + (success, message) = receiver.call(args); + } + + function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} + + // _isPacketTimeout returns true if the given packet has timed out acoording to host chain's block height and + // timestamp + function _isPacketTimeout(IbcPacket calldata packet) internal view returns (bool isTimeOut) { + return ( + isTimeOut = (packet.timeoutTimestamp != 0 && block.timestamp >= packet.timeoutTimestamp) + // TODO: check timeoutHeight.revision_number? + || (packet.timeoutHeight.revision_height != 0 && block.number >= packet.timeoutHeight.revision_height) + ); + } +} diff --git a/test/upgradeableProxy/upgrades/DispatcherV2Initializable.sol b/test/upgradeableProxy/upgrades/DispatcherV2Initializable.sol new file mode 100644 index 00000000..281b510d --- /dev/null +++ b/test/upgradeableProxy/upgrades/DispatcherV2Initializable.sol @@ -0,0 +1,22 @@ +//SPDX-License-Identifier: UNLICENSED + +pragma solidity ^0.8.9; + +import {DispatcherV2} from "./DispatcherV2.sol"; +import {LightClient} from "../../../contracts/interfaces/LightClient.sol"; +/** + * @title Dispatcher + * @author Polymer Labs + * @notice + * Contract callers call this contract to send IBC-like msg, + * which can be relayed to a rollup module on the Polymerase chain + */ + +contract DispatcherV2Initializable is DispatcherV2 { + function initialize(string memory initPortPrefix, LightClient _lightClient) public override reinitializer(2) { + __Ownable_init(); + portPrefix = initPortPrefix; + portPrefixLen = uint32(bytes(initPortPrefix).length); + lightClient = _lightClient; + } +}