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

Implement TeleporterOwnerUpgradeable #92

Merged
merged 8 commits into from
Nov 6, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,11 @@ import "./BridgeToken.sol";
import "../../Teleporter/ITeleporterMessenger.sol";
import "../../Teleporter/ITeleporterReceiver.sol";
import "../../Teleporter/SafeERC20TransferFrom.sol";
import "../../Teleporter/upgrades/TeleporterRegistry.sol";
import "../../Teleporter/upgrades/TeleporterUpgradeable.sol";
import "../../Teleporter/upgrades/TeleporterOwnerUpgradeable.sol";
import "@subnet-evm-contracts/interfaces/IWarpMessenger.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

/**
* @dev Implementation of the {IERC20Bridge} interface.
Expand All @@ -28,8 +26,7 @@ contract ERC20Bridge is
IERC20Bridge,
ITeleporterReceiver,
ReentrancyGuard,
TeleporterUpgradeable,
Ownable
TeleporterOwnerUpgradeable
{
using SafeERC20 for IERC20;

Expand Down Expand Up @@ -79,7 +76,7 @@ contract ERC20Bridge is
*/
constructor(
address teleporterRegistryAddress
) TeleporterUpgradeable(teleporterRegistryAddress) {
) TeleporterOwnerUpgradeable(teleporterRegistryAddress) {
currentChainID = WarpMessenger(WARP_PRECOMPILE_ADDRESS)
.getBlockchainID();
}
Expand Down Expand Up @@ -324,23 +321,6 @@ contract ERC20Bridge is
}
}

/**
* @dev See {TeleporterUpgradeable-updateMinTeleporterVersion}
*
* Updates the minimum Teleporter version allowed for receiving on this contract
* to the latest version registered in the {TeleporterRegistry}.
* Restricted to only owners of the contract.
* Emits a {MinTeleporterVersionUpdated} event.
*/
function updateMinTeleporterVersion() external override onlyOwner {
uint256 oldMinTeleporterVersion = minTeleporterVersion;
minTeleporterVersion = teleporterRegistry.getLatestVersion();
emit MinTeleporterVersionUpdated(
oldMinTeleporterVersion,
minTeleporterVersion
);
}

/**
* @dev Encodes the parameters for the Create action to be decoded and executed on the destination.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -636,33 +636,6 @@ contract ERC20BridgeTest is Test {
new ERC20Bridge(address(0));
}

function testUpdateMinTeleporterVersion() public {
// Check that updating minimum Teleporter version fails if it's not the contract owner.
vm.prank(address(0));
vm.expectRevert("Ownable: caller is not the owner");
erc20Bridge.updateMinTeleporterVersion();

// Check that the owner can update the minimum Teleporter version.
vm.mockCall(
MOCK_TELEPORTER_REGISTRY_ADDRESS,
abi.encodeWithSelector(
WarpProtocolRegistry.getLatestVersion.selector
),
abi.encode(2)
);
vm.expectCall(
MOCK_TELEPORTER_REGISTRY_ADDRESS,
abi.encodeWithSelector(
WarpProtocolRegistry.getLatestVersion.selector
)
);

vm.expectEmit(true, true, true, true, address(erc20Bridge));
emit MinTeleporterVersionUpdated(1, 2);
erc20Bridge.updateMinTeleporterVersion();
assertEq(erc20Bridge.minTeleporterVersion(), 2);
}

function _initMockTeleporterRegistry() internal {
vm.mockCall(
MOCK_TELEPORTER_REGISTRY_ADDRESS,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@ pragma solidity 0.8.18;
import "../../Teleporter/ITeleporterMessenger.sol";
import "../../Teleporter/SafeERC20TransferFrom.sol";
import "../../Teleporter/ITeleporterReceiver.sol";
import "../../Teleporter/upgrades/TeleporterRegistry.sol";
import "../../Teleporter/upgrades/TeleporterUpgradeable.sol";
import "../../Teleporter/upgrades/TeleporterOwnerUpgradeable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

/**
* @dev ExampleCrossChainMessenger is an example contract that demonstrates how to send and receive
Expand All @@ -21,8 +19,7 @@ import "@openzeppelin/contracts/access/Ownable.sol";
contract ExampleCrossChainMessenger is
ITeleporterReceiver,
ReentrancyGuard,
TeleporterUpgradeable,
Ownable
TeleporterOwnerUpgradeable
{
using SafeERC20 for IERC20;

Expand Down Expand Up @@ -57,7 +54,7 @@ contract ExampleCrossChainMessenger is

constructor(
address teleporterRegistryAddress
) TeleporterUpgradeable(teleporterRegistryAddress) {}
) TeleporterOwnerUpgradeable(teleporterRegistryAddress) {}

/**
* @dev See {ITeleporterReceiver-receiveTeleporterMessage}.
Expand Down Expand Up @@ -126,23 +123,6 @@ contract ExampleCrossChainMessenger is
);
}

/**
* @dev See {TeleporterUpgradeable-updateMinTeleporterVersion}
*
* Updates the minimum Teleporter version allowed for receiving on this contract
* to the latest version registered in the {TeleporterRegistry}. Also restricts this function to
* the owner of this contract.
* Emits a {MinTeleporterVersionUpdated} event.
*/
function updateMinTeleporterVersion() external override onlyOwner {
uint256 oldMinTeleporterVersion = minTeleporterVersion;
minTeleporterVersion = teleporterRegistry.getLatestVersion();
emit MinTeleporterVersionUpdated(
oldMinTeleporterVersion,
minTeleporterVersion
);
}

/**
* @dev Returns the current message from another chain.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,12 @@ pragma solidity 0.8.18;

import "../../Teleporter/ITeleporterMessenger.sol";
import "../../Teleporter/ITeleporterReceiver.sol";
import "../../Teleporter/upgrades/TeleporterRegistry.sol";
import "../../Teleporter/upgrades/TeleporterUpgradeable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "../../Teleporter/upgrades/TeleporterOwnerUpgradeable.sol";

/**
* Contract for receiving latest block hashes from another chain.
*/
contract BlockHashReceiver is
ITeleporterReceiver,
TeleporterUpgradeable,
Ownable
{
contract BlockHashReceiver is ITeleporterReceiver, TeleporterOwnerUpgradeable {
// Source chain information
bytes32 public immutable sourceChainID;
address public immutable sourcePublisherContractAddress;
Expand All @@ -41,7 +35,7 @@ contract BlockHashReceiver is
address teleporterRegistryAddress,
bytes32 publisherChainID,
address publisherContractAddress
) TeleporterUpgradeable(teleporterRegistryAddress) {
) TeleporterOwnerUpgradeable(teleporterRegistryAddress) {
sourceChainID = publisherChainID;
sourcePublisherContractAddress = publisherContractAddress;
}
Expand Down Expand Up @@ -88,23 +82,6 @@ contract BlockHashReceiver is
}
}

/**
* @dev See {TeleporterUpgradeable-updateMinTeleporterVersion}
*
* Updates the minimum Teleporter version allowed for receiving on this contract
* to the latest version registered in the {TeleporterRegistry}.
* Restricted to only owners of the contract.
* Emits a {MinTeleporterVersionUpdated} event.
*/
function updateMinTeleporterVersion() external override onlyOwner {
uint256 oldMinTeleporterVersion = minTeleporterVersion;
minTeleporterVersion = teleporterRegistry.getLatestVersion();
emit MinTeleporterVersionUpdated(
oldMinTeleporterVersion,
minTeleporterVersion
);
}

/**
* @dev Returns the latest block information.
*/
Expand Down
2 changes: 1 addition & 1 deletion contracts/src/Teleporter/upgrades/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ The `TeleporterUpgradeable` contract saves the Teleporter registry in a state va

Every derived contract of `TeleporterUpgradeable` must implement `TeleporterUpgradeable.updateMinTeleporterVersion`, which updates the `minTeleporterVersion` used by the `onlyAllowedTeleporter` modifier and emits the `MinTeleporterVersionUpdated` event. The `updateMinTeleporterVersion` function should be called by the dapp when it completes delivery of messages from the old Teleporter contract, and now wants to update the `minTeleporterVersion` to only allow the new Teleporter version.

To prevent anyone from calling the dapp's `updateMinTeleporterVersion`, which would disallow messages from old Teleporter versions from being received, this function should be safeguarded with access controls. For example, the ERC20Bridge inherits `Ownable`, and restricts the `updateMinTeleporterVersion` function call to the owner of the contract.
To prevent anyone from calling the dapp's `updateMinTeleporterVersion`, which would disallow messages from old Teleporter versions from being received, this function should be safeguarded with access controls. For example, [TeleporterOwnerUpgrade](./TeleporterOwnerUpgradeable.sol) is a contract that inherits `TeleporterUpgradeable` and restricts `updateMinTeleporterVersion` calls to the owner of the contract. The [ERC20Bridge](../../CrossChainApplications/ERC20Bridge/ERC20Bridge.sol) contract is an example of inheriting `TeleporterOwnerUpgradeable`.

```solidity
function updateMinTeleporterVersion() external override onlyOwner {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// (c) 2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: Ecosystem

pragma solidity 0.8.18;

import "./TeleporterUpgradeable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

/**
* @dev Contract that implements the {TeleporterUpgradeable} interface and allows
* only owners of the contract to update the minimum Teleporter version.
*/
abstract contract TeleporterOwnerUpgradeable is TeleporterUpgradeable, Ownable {
constructor(
address teleporterRegistryAddress
) TeleporterUpgradeable(teleporterRegistryAddress) {}

/**
* @dev See {TeleporterUpgradeable-updateMinTeleporterVersion}
*
* Updates the minimum Teleporter version allowed for receiving on this contract
* to the latest version registered in the {TeleporterRegistry}.
* Restricted to only owners of the contract.
* Emits a {MinTeleporterVersionUpdated} event if the minimum Teleporter version
* was updated.
*/
function updateMinTeleporterVersion() external override onlyOwner {
uint256 oldMinTeleporterVersion = minTeleporterVersion;
minTeleporterVersion = teleporterRegistry.getLatestVersion();
if (minTeleporterVersion > oldMinTeleporterVersion) {
emit MinTeleporterVersionUpdated(
oldMinTeleporterVersion,
minTeleporterVersion
);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// (c) 2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: Ecosystem

pragma solidity 0.8.18;

import "forge-std/Test.sol";
import "../TeleporterOwnerUpgradeable.sol";
import "./TeleporterUpgradeableTests.t.sol";

contract ExampleOwnerUpgradeableApp is TeleporterOwnerUpgradeable {
constructor(
address teleporterRegistryAddress
) TeleporterOwnerUpgradeable(teleporterRegistryAddress) {}
}

contract TeleporterOwnerUpgradeableTest is TeleporterUpgradeableTest {
ExampleOwnerUpgradeableApp public app;
address public constant MOCK_INVALID_OWNER_ADDRESS =
0xd54e3E251b9b0EEd3ed70A858e927bbC2659587d;

function setUp() public virtual override {
TeleporterUpgradeableTest.setUp();
app = new ExampleOwnerUpgradeableApp(address(teleporterRegistry));
}

function testOwnerUpdateMinTeleporterVersion() public {
uint256 minTeleporterVersion = app.minTeleporterVersion();

// Check that call to update minimum Teleporter version reverts for non-owners
vm.prank(MOCK_INVALID_OWNER_ADDRESS);
vm.expectRevert("Ownable: caller is not the owner");
app.updateMinTeleporterVersion();

// Check that minimum Teleporter version was not updated
assertEq(app.minTeleporterVersion(), minTeleporterVersion);

_addProtocolVersion(teleporterRegistry, teleporterAddress);

// Check that call to update minimum Teleporter version succeeds for owners
vm.prank(address(this));
vm.expectEmit(true, true, true, true, address(app));
emit MinTeleporterVersionUpdated(
minTeleporterVersion,
minTeleporterVersion + 1
);
app.updateMinTeleporterVersion();

assertEq(app.minTeleporterVersion(), minTeleporterVersion + 1);
}

function testOwnerTransfer() public {
uint256 minTeleporterVersion = app.minTeleporterVersion();
_addProtocolVersion(teleporterRegistry, teleporterAddress);

// Check that call to transfer ownership reverts for non-owners
vm.prank(MOCK_INVALID_OWNER_ADDRESS);
vm.expectRevert("Ownable: caller is not the owner");
app.transferOwnership(address(0));

// Check that ownership was not transferred
assertEq(app.owner(), address(this));

// Check that call for non owners reverts
vm.prank(MOCK_INVALID_OWNER_ADDRESS);
vm.expectRevert("Ownable: caller is not the owner");
app.updateMinTeleporterVersion();

// Check that after ownership transfer call succeeds
address newOwner = address(0x123);
app.transferOwnership(newOwner);
vm.prank(newOwner);
vm.expectEmit(true, true, true, true, address(app));
emit MinTeleporterVersionUpdated(
minTeleporterVersion,
minTeleporterVersion + 1
);
app.updateMinTeleporterVersion();

// Check that call with old owner reverts
vm.expectRevert("Ownable: caller is not the owner");
app.updateMinTeleporterVersion();
}

function testRenounceOwnership() public {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we have a test case where renounceOwnership is called successfully, and then assert that the previous owner is unable to update the min teleporter version afterwards?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup the last few lines check this, but lmk if this should be made more clear or separated.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah my bad, I was just reading it wrong before. 👍

One minor nit, can we have a MOCK_OWNER_ADDRESS value that we use for pranking & transferring ownership to in this test suite rather than using the teleporterAddress? I think it would make it slightly more clear and easier to reason about.

// Check that call to renounce ownership reverts for non-owners
vm.prank(MOCK_INVALID_OWNER_ADDRESS);
vm.expectRevert("Ownable: caller is not the owner");
app.renounceOwnership();

// Check that ownership was not renounced
assertEq(app.owner(), address(this));

// Check that update Teleporter version call for owner succeeds
app.updateMinTeleporterVersion();

// Check that after ownership renounce call reverts
app.renounceOwnership();
vm.expectRevert("Ownable: caller is not the owner");
app.updateMinTeleporterVersion();
}
}
Loading