Skip to content

Commit

Permalink
Merge pull request #50 from siburu/channel-upgrade
Browse files Browse the repository at this point in the history
Support for Channel Upgrade
  • Loading branch information
siburu authored Aug 30, 2024
2 parents 9faacb5 + 04c9ec2 commit b2579e9
Show file tree
Hide file tree
Showing 24 changed files with 1,826 additions and 63 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
build/
cache/
node_modules/
out/
20 changes: 16 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
FORGE ?= forge
ABIGEN ?= docker run -u $$(id -u):$$(id -g) -v .:/workspace -w /workspace -it ethereum/client-go:alltools-v1.14.0 abigen
ABIGEN ?= docker run -v .:/workspace -w /workspace -it ethereum/client-go:alltools-v1.14.0 abigen

DOCKER := $(shell which docker)

Expand All @@ -16,19 +16,31 @@ submodule:
git submodule update --init
cd ./yui-ibc-solidity && npm install

.PHONY: dep
dep:
$(DOCKER) run --rm -v $$PWD:$$PWD -w $$PWD node:20 npm i
cd ./yui-ibc-solidity && $(DOCKER) run --rm -v $$PWD:$$PWD -w $$PWD node:20 npm i

.PHONY: compile
compile:
cp contract/*.sol ./yui-ibc-solidity/contracts/
$(FORGE) build
$(FORGE) build --config-path ./yui-ibc-solidity/foundry.toml

.PHONY: abigen
abigen: compile
@for a in IBCHandler Multicall3; do \
@mkdir -p ./build/abi
@for a in IBCHandler IIBCChannelUpgradableModule; do \
b=$$(echo $$a | tr '[A-Z]' '[a-z]'); \
mkdir -p ./build/abi ./pkg/contract/$$b; \
mkdir -p ./pkg/contract/$$b; \
jq -r '.abi' ./yui-ibc-solidity/out/$$a.sol/$$a.json > ./build/abi/$$a.abi; \
$(ABIGEN) --abi ./build/abi/$$a.abi --pkg $$b --out ./pkg/contract/$$b/$$b.go; \
done
@for a in Multicall3 IIBCContractUpgradableModule; do \
b=$$(echo $$a | tr '[A-Z]' '[a-z]'); \
mkdir -p ./pkg/contract/$$b; \
jq -r '.abi' ./out/$$a.sol/$$a.json > ./build/abi/$$a.abi; \
$(ABIGEN) --abi ./build/abi/$$a.abi --pkg $$b --out ./pkg/contract/$$b/$$b.go; \
done

.PHONY: proto-gen proto-update-deps
proto-gen:
Expand Down
189 changes: 189 additions & 0 deletions contracts/IBCContractUpgradableModule.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.20;

import {IIBCContractUpgradableModule, IIBCContractUpgradableModuleErrors} from "./IIBCContractUpgradableModule.sol";

import {IBCChannelUpgradableModuleBase} from "@hyperledger-labs/yui-ibc-solidity/contracts/apps/commons/IBCChannelUpgradableModule.sol";
import {Channel, UpgradeFields} from "@hyperledger-labs/yui-ibc-solidity/contracts/proto/Channel.sol";
import {IIBCHandler} from "@hyperledger-labs/yui-ibc-solidity/contracts/core/25-handler/IIBCHandler.sol";

abstract contract IBCContractUpgradableModuleBase is
IBCChannelUpgradableModuleBase,
IIBCContractUpgradableModule,
IIBCContractUpgradableModuleErrors
{
// ------------------- Storage ------------------- //

// NOTE: A module should set an initial appVersion struct in contract constructor or initializer
mapping(string appVersion => AppInfo) internal appInfos;

// ------------------- Modifiers ------------------- //

modifier onlyContractUpgrader() {
address msgSender = _msgSender();
if (!_isContractUpgrader(msgSender)) {
revert IBCContractUpgradableModuleUnauthorizedUpgrader(msgSender);
}
_;
}

// ------------------- Functions ------------------- //

/**
* @dev See {IIBCContractUpgradableModule-getAppinfoproposal}
*/
function getAppInfoProposal(string calldata version)
external
view
override(IIBCContractUpgradableModule)
returns (AppInfo memory)
{
return appInfos[version];
}

/**
* @dev See {IIBCContractUpgradableModule-proposeAppVersion}
*/
function proposeAppVersion(string calldata version, AppInfo calldata appInfo_)
external
override(IIBCContractUpgradableModule)
onlyContractUpgrader
{
if (appInfo_.implementation == address(0)) {
revert IBCContractUpgradableModuleAppInfoProposedWithZeroImpl();
}

AppInfo storage appInfo = appInfos[version];
if (appInfo.implementation != address(0)) {
revert IBCContractUpgradableModuleAppInfoIsAlreadySet();
}

appInfos[version] = appInfo_;
}

/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override(IBCChannelUpgradableModuleBase)
returns (bool)
{
return super.supportsInterface(interfaceId) ||
interfaceId == type(IIBCContractUpgradableModule).interfaceId;
}

/**
* @dev See {IIBCModuleUpgrade-onChanUpgradeInit}
*/
function onChanUpgradeInit(
string calldata portId,
string calldata channelId,
uint64 upgradeSequence,
UpgradeFields.Data calldata proposedUpgradeFields
)
public
view
virtual
override(IBCChannelUpgradableModuleBase)
onlyIBC
returns (string memory version)
{
version = super.onChanUpgradeInit(portId, channelId, upgradeSequence, proposedUpgradeFields);

(Channel.Data memory channel, bool found) = IIBCHandler(ibcAddress()).getChannel(portId, channelId);
if (!found) {
revert IBCContractUpgradableModuleChannelNotFound(portId, channelId);
}

_prepareContractUpgrade(channel.version, version);
}

/**
* @dev See {IIBCModuleUpgrade-onChanUpgradeTry}
*/
function onChanUpgradeTry(
string calldata portId,
string calldata channelId,
uint64 upgradeSequence,
UpgradeFields.Data calldata proposedUpgradeFields
)
public
view
virtual
override(IBCChannelUpgradableModuleBase)
onlyIBC
returns (string memory version)
{
version = super.onChanUpgradeTry(portId, channelId, upgradeSequence, proposedUpgradeFields);

(Channel.Data memory channel, bool found) = IIBCHandler(ibcAddress()).getChannel(portId, channelId);
if (!found) {
revert IBCContractUpgradableModuleChannelNotFound(portId, channelId);
}

_prepareContractUpgrade(channel.version, version);
}

/**
* @dev See {IIBCModuleUpgrade-onChanUpgradeOpen}
*/
function onChanUpgradeOpen(
string calldata portId,
string calldata channelId,
uint64 upgradeSequence
)
public
virtual
override(IBCChannelUpgradableModuleBase)
onlyIBC
{
super.onChanUpgradeOpen(portId, channelId, upgradeSequence);

(Channel.Data memory channel, bool found) = IIBCHandler(ibcAddress()).getChannel(portId, channelId);
if (!found) {
revert IBCContractUpgradableModuleChannelNotFound(portId, channelId);
}

_upgradeContract(channel.version);
}

// ------------------- Internal Functions ------------------- //

function _isContractUpgrader(address msgSender) internal view virtual returns (bool);

function _doUpgradeContract(address implementation, bytes memory initialCalldata) internal virtual;

// ------------------- Private Functions ------------------- //

function _prepareContractUpgrade(string memory version, string memory newVersion) private view {
if (!_compareString(version, newVersion)) {
AppInfo storage appInfo = appInfos[newVersion];
if (appInfo.implementation == address(0)) {
revert IBCContractUpgradableModuleAppInfoNotProposedYet();
}
if (appInfo.consumed) {
revert IBCContractUpgradableModuleAlreadyConsumedAppInfo();
}
}
}

function _compareString(string memory a, string memory b) private pure returns (bool) {
if (bytes(a).length != bytes(b).length) {
return false;
}
return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b));
}

function _upgradeContract(string memory newVersion) private {
AppInfo storage appInfo = appInfos[newVersion];

if (appInfo.implementation != address(0) && !appInfo.consumed) {
appInfo.consumed = true;
_doUpgradeContract(appInfo.implementation, appInfo.initialCalldata);
delete appInfo.initialCalldata;
}
}
}
112 changes: 112 additions & 0 deletions contracts/IBCContractUpgradableUUPSMockApp.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.20;

import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

import {IBCContractUpgradableModuleBase} from "./IBCContractUpgradableModule.sol";

import {IBCMockApp} from "@hyperledger-labs/yui-ibc-solidity/contracts/apps/mock/IBCMockApp.sol";
import {IBCAppBase} from "@hyperledger-labs/yui-ibc-solidity/contracts/apps/commons/IBCAppBase.sol";
import {IBCChannelUpgradableModuleBase} from "@hyperledger-labs/yui-ibc-solidity/contracts/apps/commons/IBCChannelUpgradableModule.sol";
import {IIBCHandler} from "@hyperledger-labs/yui-ibc-solidity/contracts/core/25-handler/IIBCHandler.sol";

contract IBCContractUpgradableUUPSMockApp is
UUPSUpgradeable,
IBCMockApp,
IBCContractUpgradableModuleBase
{
address immutable _self; // implementation's address, which is set by the constructor
address immutable _deployer; // contract deployer, which is set by the constructor
constructor(IIBCHandler ibcHandler_) IBCMockApp(ibcHandler_) {
_self = address(this);
_deployer = msg.sender;
}

// ------------------- Functions ------------------- //

function self() external view returns(address) {
return _self;
}

/**
* @dev See {Ownable-owner}.
*/
function owner() public view virtual override(Ownable) returns (address) {
return _deployer;
}

/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override(IBCAppBase, IBCContractUpgradableModuleBase)
returns (bool)
{
return super.supportsInterface(interfaceId) ||
interfaceId == type(UUPSUpgradeable).interfaceId;
}

// ------------------- Internal Functions ------------------- //

function __IBCContractUpgradableUUPSMockApp_init(string memory initialVersion) internal initializer {
__UUPSUpgradeable_init();

AppInfo storage appInfo = appInfos[initialVersion];
appInfo.implementation = _self;
appInfo.consumed = true;
}

/**
* @dev See{IBCChannelUpgradableModuleBase-_isAuthorizedUpgrader}
*/
function _isAuthorizedUpgrader(string calldata, string calldata, address msgSender)
internal
view
virtual
override(IBCChannelUpgradableModuleBase)
returns (bool)
{
return _isContractUpgrader(msgSender);
}

/**
* @dev See{IBCContractUpgradableModuleBase-_isContractUpgrader}
*/
function _isContractUpgrader(address msgSender)
internal
view
virtual
override(IBCContractUpgradableModuleBase)
returns (bool)
{
return msgSender == owner() || msgSender == address(this);
}

/**
* @dev See{IBCContractUpgradableModuleBase-_upgradeContract}
*/
function _doUpgradeContract(address implementation, bytes memory initialCalldata)
internal
virtual
override(IBCContractUpgradableModuleBase)
{
// if there is no implementation update, nothing happens here
if (ERC1967Utils.getImplementation() != implementation) {
ERC1967Utils.upgradeToAndCall(implementation, initialCalldata);
}
}

/**
* @dev See {UUPSupgradeable-_authorizeupgrade}
*/
function _authorizeUpgrade(address msgSender) internal view virtual override(UUPSUpgradeable) {
if (!_isContractUpgrader(msgSender)) {
revert IBCContractUpgradableModuleUnauthorizedUpgrader(msgSender);
}
}
}
46 changes: 46 additions & 0 deletions contracts/IIBCContractUpgradableModule.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.20;

interface IIBCContractUpgradableModuleErrors {
// ------------------- Errors ------------------- //

error IBCContractUpgradableModuleUnauthorizedUpgrader(address msgSender);
error IBCContractUpgradableModuleAppInfoProposedWithZeroImpl();
error IBCContractUpgradableModuleAppInfoNotProposedYet();
error IBCContractUpgradableModuleAppInfoIsAlreadySet();
error IBCContractUpgradableModuleChannelNotFound(string portId, string channelId);
error IBCContractUpgradableModuleAlreadyConsumedAppInfo();
}

interface IIBCContractUpgradableModule {
// ------------------- Data Structures ------------------- //

/**
* @dev Proposed AppInfo data
* @param implemantation the new implementation address
* @param initialCalldata the first function call's calldata
* @param consumed the flag that specifies if this implementation is already deployed
*/
struct AppInfo {
address implementation;
bytes initialCalldata;
bool consumed;
}

// ------------------- Functions ------------------- //

/**
* @dev Returns the proposed AppInfo for the given version
*/
function getAppInfoProposal(string calldata version) external view returns (AppInfo memory);

/**
* @dev Propose an Appinfo for the given version
* @notice This function is only callable by an authorized upgrader.
* To upgrade the IBC module contract along with a channel upgrade, the upgrader must
* call this function before calling `channelUpgradeInit` or `channelUpgradeTry` of the IBC handler.
*/
function proposeAppVersion(string calldata version, AppInfo calldata appInfo) external;

}

File renamed without changes.
Loading

0 comments on commit b2579e9

Please sign in to comment.