Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Channel Upgrade #50

Merged
merged 8 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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