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

Poc: IBC-solidity-governance #172

Closed
wants to merge 7 commits into from
Closed
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
73 changes: 39 additions & 34 deletions contracts/ICS20Transfer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,36 @@ import { IICS26Router } from "./interfaces/IICS26Router.sol";
import { IICS26RouterMsgs } from "./msgs/IICS26RouterMsgs.sol";
import { IBCERC20 } from "./utils/IBCERC20.sol";
import { Escrow } from "./utils/Escrow.sol";
import { Pausable } from "@openzeppelin/utils/Pausable.sol";

using SafeERC20 for IERC20;

/*
* Things not handled yet:
* - Separate escrow balance tracking
* - Related to escrow ^: invariant checking (where to implement that?)
/**
* @title ICS20Transfer
* @notice Implements the ICS20 token transfer logic with pausable functionality.
*/
contract ICS20Transfer is IIBCApp, IICS20Transfer, IICS20Errors, Ownable, ReentrancyGuardTransient, Multicall {
contract ICS20Transfer is IIBCApp, IICS20Transfer, IICS20Errors, Ownable, ReentrancyGuardTransient, Multicall, Pausable {
/// @notice The escrow contract address
IEscrow private immutable ESCROW;
IEscrow private ESCROW;

/// @notice Mapping of non-native denoms to their respective IBCERC20 contracts created here
mapping(string denom => IBCERC20 ibcERC20Contract) private _ibcDenomContracts;

/// @param owner_ The owner of the contract
constructor(address owner_) Ownable(owner_) {
address private immutable SAFE_ADDRESS;

/// @notice Constructor to initialize the contract with the Safe address
/// @param _safeAddress The address of the Safe multisig
constructor(address _safeAddress) Ownable(address(0xdead)) {
SAFE_ADDRESS = _safeAddress; // This should be hardcoded after deployment in a real setup.
}

/// @notice Initialize the contract
/// @param _safeAddress The Safe multisig address
function initialize(address _safeAddress) external {
require(owner() == address(0), "Already initialized");
require(_safeAddress == SAFE_ADDRESS, "Only Safe can initialize");

_transferOwnership(SAFE_ADDRESS); // Transfer ownership to Safe
ESCROW = new Escrow(address(this));
}

Expand All @@ -54,18 +68,19 @@ contract ICS20Transfer is IIBCApp, IICS20Transfer, IICS20Errors, Ownable, Reentr
external
view
override
whenNotPaused
returns (IICS26RouterMsgs.MsgSendPacket memory)
{
return ICS20Lib.newMsgSendPacketV1(sender, msg_);
}

/// @inheritdoc IICS20Transfer
function sendTransfer(SendTransferMsg calldata msg_) external override returns (uint32) {
function sendTransfer(SendTransferMsg calldata msg_) external override whenNotPaused returns (uint32) {
return IICS26Router(owner()).sendPacket(ICS20Lib.newMsgSendPacketV1(_msgSender(), msg_));
}

/// @inheritdoc IIBCApp
function onSendPacket(OnSendPacketCallback calldata msg_) external onlyOwner nonReentrant {
function onSendPacket(OnSendPacketCallback calldata msg_) external onlyOwner nonReentrant whenNotPaused {
require(
keccak256(bytes(msg_.payload.version)) == keccak256(bytes(ICS20Lib.ICS20_VERSION)),
ICS20UnexpectedVersion(ICS20Lib.ICS20_VERSION, msg_.payload.version)
Expand All @@ -76,36 +91,30 @@ contract ICS20Transfer is IIBCApp, IICS20Transfer, IICS20Errors, Ownable, Reentr

require(packetData.amount > 0, ICS20InvalidAmount(packetData.amount));

// Note that if we use address instead of strings in the packetDataJson field definition
//we can avoid the next line operation and save extra gas
address sender = ICS20Lib.mustHexStringToAddress(packetData.sender);

// only the sender in the payload or this contract (sendTransfer) can send the packet
require(msg_.sender == sender || msg_.sender == address(this), ICS20UnauthorizedPacketSender(msg_.sender));

(address erc20Address, bool originatorChainIsSource) =
getSendERC20AddressAndSource(msg_.payload.sourcePort, msg_.sourceChannel, packetData);

// transfer the tokens to us (requires the allowance to be set)
_transferFrom(sender, address(ESCROW), erc20Address, packetData.amount);

if (!originatorChainIsSource) {
// receiver chain is source: burn the vouchers
IBCERC20 ibcERC20 = IBCERC20(erc20Address);
ibcERC20.burn(packetData.amount);
}
}

/// @inheritdoc IIBCApp
function onRecvPacket(OnRecvPacketCallback calldata msg_) external onlyOwner nonReentrant returns (bytes memory) {
// Since this function mostly returns acks, also when it fails, the ics26router (the caller) will log the ack
function onRecvPacket(OnRecvPacketCallback calldata msg_) external onlyOwner nonReentrant whenNotPaused returns (bytes memory) {
if (keccak256(bytes(msg_.payload.version)) != keccak256(bytes(ICS20Lib.ICS20_VERSION))) {
// TODO: Figure out if should actually error out, or if just error acking is enough
return ICS20Lib.errorAck(abi.encodePacked("unexpected version: ", msg_.payload.version));
}

ICS20Lib.FungibleTokenPacketData memory packetData =
abi.decode(msg_.payload.value, (ICS20Lib.FungibleTokenPacketData));

(address erc20Address, bool originatorChainIsSource) = getReceiveERC20AddressAndSource(
msg_.payload.sourcePort, msg_.sourceChannel, msg_.payload.destPort, msg_.destinationChannel, packetData
);
Expand All @@ -120,19 +129,16 @@ contract ICS20Transfer is IIBCApp, IICS20Transfer, IICS20Errors, Ownable, Reentr
}

if (originatorChainIsSource) {
// sender is source, so we mint vouchers
// NOTE: getReceiveTokenContractAndSource has already created a contract if it didn't exist
IBCERC20(erc20Address).mint(packetData.amount);
}

// transfer the tokens to the receiver
ESCROW.send(IERC20(erc20Address), receiver, packetData.amount);

return ICS20Lib.SUCCESSFUL_ACKNOWLEDGEMENT_JSON;
}

/// @inheritdoc IIBCApp
function onAcknowledgementPacket(OnAcknowledgementPacketCallback calldata msg_) external onlyOwner nonReentrant {
function onAcknowledgementPacket(OnAcknowledgementPacketCallback calldata msg_) external onlyOwner nonReentrant whenNotPaused {
ICS20Lib.FungibleTokenPacketData memory packetData =
abi.decode(msg_.payload.value, (ICS20Lib.FungibleTokenPacketData));

Expand All @@ -144,39 +150,38 @@ contract ICS20Transfer is IIBCApp, IICS20Transfer, IICS20Errors, Ownable, Reentr
}

/// @inheritdoc IIBCApp
function onTimeoutPacket(OnTimeoutPacketCallback calldata msg_) external onlyOwner nonReentrant {
function onTimeoutPacket(OnTimeoutPacketCallback calldata msg_) external onlyOwner nonReentrant whenNotPaused {
ICS20Lib.FungibleTokenPacketData memory packetData =
abi.decode(msg_.payload.value, (ICS20Lib.FungibleTokenPacketData));
(address erc20Address,) = getSendERC20AddressAndSource(msg_.payload.sourcePort, msg_.sourceChannel, packetData);
_refundTokens(packetData, erc20Address);
}

/// @notice Pause the contract (only callable by owner)
function pause() external onlyOwner {
_pause();
}

/// @notice Unpause the contract (only callable by owner)
function unpause() external onlyOwner {
_unpause();
}

/// @notice Refund the tokens to the sender
/// @param packetData The packet data
/// @param erc20Address The address of the ERC20 contract
function _refundTokens(ICS20Lib.FungibleTokenPacketData memory packetData, address erc20Address) private {
address refundee = ICS20Lib.mustHexStringToAddress(packetData.sender);
ESCROW.send(IERC20(erc20Address), refundee, packetData.amount);
}

/// @notice Transfer tokens from sender to receiver
/// @param sender The sender of the tokens
/// @param receiver The receiver of the tokens
/// @param tokenContract The address of the token contract
/// @param amount The amount of tokens to transfer
function _transferFrom(address sender, address receiver, address tokenContract, uint256 amount) private {
// we snapshot current balance of this token
uint256 ourStartingBalance = IERC20(tokenContract).balanceOf(receiver);

IERC20(tokenContract).safeTransferFrom(sender, receiver, amount);

// check what this particular ERC20 implementation actually gave us, since it doesn't
// have to be at all related to the _amount
uint256 actualEndingBalance = IERC20(tokenContract).balanceOf(receiver);

uint256 expectedEndingBalance = ourStartingBalance + amount;
// a very strange ERC20 may trigger this condition, if we didn't have this we would
// underflow, so it's mostly just an error message printer
require(
actualEndingBalance > ourStartingBalance && actualEndingBalance == expectedEndingBalance,
ICS20UnexpectedERC20Balance(expectedEndingBalance, actualEndingBalance)
Expand Down
45 changes: 33 additions & 12 deletions contracts/ICS26Router.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,32 @@ import { ILightClientMsgs } from "./msgs/ILightClientMsgs.sol";
import { IICS04ChannelMsgs } from "./msgs/IICS04ChannelMsgs.sol";
import { ReentrancyGuardTransient } from "@openzeppelin/utils/ReentrancyGuardTransient.sol";
import { Multicall } from "@openzeppelin/utils/Multicall.sol";

import { Pausable } from "@openzeppelin/utils/Pausable.sol";
/// @title IBC Eureka Router
/// @notice ICS26Router is the router for the IBC Eureka protocol
contract ICS26Router is IICS26Router, IICS26RouterErrors, Ownable, ReentrancyGuardTransient, Multicall {
contract ICS26Router is IICS26Router, IICS26RouterErrors, Ownable, ReentrancyGuardTransient, Multicall, Pausable {

/// @dev portId => IBC Application contract
mapping(string portId => IIBCApp app) private apps;

/// @inheritdoc IICS26Router
IIBCStore public immutable IBC_STORE;
IIBCStore public IBC_STORE;
/// @notice ICSCore implements IICS02Client and IICS04Channel
address private immutable ICS_CORE;
address private ICS_CORE;

address private immutable SAFE_ADDRESS;

constructor(address _safeAddress) Ownable(address(0xdead)) {
SAFE_ADDRESS = _safeAddress; // This should not be passed as input but instead Should be an hardcoded constant to be set after safe multisig deployment and before this contracts gets deployed.
// Setting now in input for easy testing.
}

constructor(address owner) Ownable(owner) {
ICS_CORE = address(new ICSCore(owner)); // using the same owner
IBC_STORE = new IBCStore(address(this)); // using this contract as the owner
function initialize(address _safeAddress) external {
require(owner() == address(0), "Already initialized");
require(_safeAddress == SAFE_ADDRESS, "Only Safe can initialize");
_transferOwnership(SAFE_ADDRESS); // Transfer ownership to Safe
ICS_CORE = address(new ICSCore(SAFE_ADDRESS));
IBC_STORE = new IBCStore(address(this));
}

/// @inheritdoc IICS26Router
Expand Down Expand Up @@ -61,7 +72,7 @@ contract ICS26Router is IICS26Router, IICS26RouterErrors, Ownable, ReentrancyGua
/// @param portId The port identifier
/// @param app The address of the IBC application contract
/// @inheritdoc IICS26Router
function addIBCApp(string calldata portId, address app) external {
function addIBCApp(string calldata portId, address app) external whenNotPaused {
string memory newPortId;
if (bytes(portId).length != 0) {
Ownable._checkOwner();
Expand All @@ -82,7 +93,7 @@ contract ICS26Router is IICS26Router, IICS26RouterErrors, Ownable, ReentrancyGua
/// @param msg_ The message for sending packets
/// @return The sequence number of the packet
/// @inheritdoc IICS26Router
function sendPacket(MsgSendPacket calldata msg_) external nonReentrant returns (uint32) {
function sendPacket(MsgSendPacket calldata msg_) external nonReentrant whenNotPaused returns (uint32) {
// TODO: Support multi-payload packets #93
require(msg_.payloads.length == 1, IBCMultiPayloadPacketNotSupported());
Payload calldata payload = msg_.payloads[0];
Expand Down Expand Up @@ -123,7 +134,7 @@ contract ICS26Router is IICS26Router, IICS26RouterErrors, Ownable, ReentrancyGua
/// @notice Receives a packet
/// @param msg_ The message for receiving packets
/// @inheritdoc IICS26Router
function recvPacket(MsgRecvPacket calldata msg_) external nonReentrant {
function recvPacket(MsgRecvPacket calldata msg_) external nonReentrant whenNotPaused{
// TODO: Support multi-payload packets #93
require(msg_.packet.payloads.length == 1, IBCMultiPayloadPacketNotSupported());
Payload calldata payload = msg_.packet.payloads[0];
Expand Down Expand Up @@ -179,7 +190,7 @@ contract ICS26Router is IICS26Router, IICS26RouterErrors, Ownable, ReentrancyGua
/// @notice Acknowledges a packet
/// @param msg_ The message for acknowledging packets
/// @inheritdoc IICS26Router
function ackPacket(MsgAckPacket calldata msg_) external nonReentrant {
function ackPacket(MsgAckPacket calldata msg_) external nonReentrant whenNotPaused{
// TODO: Support multi-payload packets #93
require(msg_.packet.payloads.length == 1, IBCMultiPayloadPacketNotSupported());
Payload calldata payload = msg_.packet.payloads[0];
Expand Down Expand Up @@ -233,7 +244,7 @@ contract ICS26Router is IICS26Router, IICS26RouterErrors, Ownable, ReentrancyGua
/// @notice Timeouts a packet
/// @param msg_ The message for timing out packets
/// @inheritdoc IICS26Router
function timeoutPacket(MsgTimeoutPacket calldata msg_) external nonReentrant {
function timeoutPacket(MsgTimeoutPacket calldata msg_) external nonReentrant whenNotPaused{
// TODO: Support multi-payload packets #93
require(msg_.packet.payloads.length == 1, IBCMultiPayloadPacketNotSupported());
Payload calldata payload = msg_.packet.payloads[0];
Expand Down Expand Up @@ -282,6 +293,16 @@ contract ICS26Router is IICS26Router, IICS26RouterErrors, Ownable, ReentrancyGua
emit TimeoutPacket(msg_.packet);
}

/// @notice Pause the contract (only callable by owner)
function pause() external onlyOwner {
_pause();
}

/// @notice Unpause the contract (only callable by owner)
function unpause() external onlyOwner {
_unpause();
}

/// @notice Writes a packet acknowledgement and emits an event
/// @param packet The packet to acknowledge
/// @param acks The acknowledgement
Expand Down
28 changes: 20 additions & 8 deletions contracts/ICSCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,29 @@ import { IBCIdentifiers } from "./utils/IBCIdentifiers.sol";
import { ILightClient } from "./interfaces/ILightClient.sol";
import { IICS02ClientErrors } from "./errors/IICS02ClientErrors.sol";
import { Ownable } from "@openzeppelin/access/Ownable.sol";
import { Pausable } from "@openzeppelin/utils/Pausable.sol";

contract ICSCore is IICS02Client, IICS04Channel, IICS02ClientErrors, Ownable {
contract ICSCore is IICS02Client, IICS04Channel, IICS02ClientErrors, Ownable, Pausable {
/// @dev channelId => channel
mapping(string channelId => Channel channel) private channels;
/// @dev clientId => light client contract
mapping(string clientId => ILightClient client) private clients;
/// @dev clientType => nextClientSeq
mapping(string clientType => uint32 nextClientSeq) private nextClientSeq;

/// @param owner_ The owner of the contract
constructor(address owner_) Ownable(owner_) { }
address private immutable SAFE_ADDRESS;

constructor(address _safeAddress) Ownable(address(0xdead)) {
SAFE_ADDRESS = _safeAddress; // This should not be passed as input but instead Should be an hardcoded constant to be set after safe multisig deployment and before this contracts gets deployed.
// Setting now in input for easy testing.
}

function initialize(address _safeAddress) external {
require(owner() == address(0), "Already initialized");
require(_safeAddress == SAFE_ADDRESS, "Only Safe can initialize");

_transferOwnership(SAFE_ADDRESS); // Transfer ownership to Safe
}

/// @notice Generates the next client identifier
/// @param clientType The client type
Expand Down Expand Up @@ -53,7 +65,7 @@ contract ICSCore is IICS02Client, IICS04Channel, IICS02ClientErrors, Ownable {
Channel calldata channel,
address client
)
external
external whenNotPaused
returns (string memory)
{
string memory clientId = getNextClientId(clientType);
Expand All @@ -68,7 +80,7 @@ contract ICSCore is IICS02Client, IICS04Channel, IICS02ClientErrors, Ownable {
}

/// @inheritdoc IICS02Client
function migrateClient(string calldata subjectClientId, string calldata substituteClientId) external onlyOwner {
function migrateClient(string calldata subjectClientId, string calldata substituteClientId) external onlyOwner whenNotPaused {
getClient(subjectClientId); // Ensure subject client exists
ILightClient substituteClient = getClient(substituteClientId);

Expand All @@ -84,19 +96,19 @@ contract ICSCore is IICS02Client, IICS04Channel, IICS02ClientErrors, Ownable {
string calldata clientId,
bytes calldata updateMsg
)
external
external whenNotPaused
returns (ILightClient.UpdateResult)
{
return getClient(clientId).updateClient(updateMsg);
}

/// @inheritdoc IICS02Client
function submitMisbehaviour(string calldata clientId, bytes calldata misbehaviourMsg) external {
function submitMisbehaviour(string calldata clientId, bytes calldata misbehaviourMsg) external whenNotPaused{
getClient(clientId).misbehaviour(misbehaviourMsg);
}

/// @inheritdoc IICS02Client
function upgradeClient(string calldata clientId, bytes calldata upgradeMsg) external {
function upgradeClient(string calldata clientId, bytes calldata upgradeMsg) external whenNotPaused{
getClient(clientId).upgradeClient(upgradeMsg);
}
}
Loading
Loading