Add Interchain Gas Paymaster to XAppConnectionManager (#288)

* Add IInterchainGasPaymaster, add interchainGasPaymaster to XAppConnectionManager

* Tests for xAppConnectionManager

* Nit

* Clean up tests, have InterchainGasPaymaster implement IInterchainGasPaymaster

* Explain compatibility

* Nit

* Another nit

* Better comments, rm compatibility
pull/292/head
Trevor Porter 3 years ago committed by GitHub
parent fad3739409
commit 69c37c455b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      solidity/core/contracts/InterchainGasPaymaster.sol
  2. 55
      solidity/core/contracts/XAppConnectionManager.sol
  3. 18
      solidity/core/interfaces/IInterchainGasPaymaster.sol
  4. 138
      solidity/core/test/xAppConnectionManager.test.ts

@ -1,6 +1,8 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
// ============ Internal Imports ============
import {IInterchainGasPaymaster} from "../interfaces/IInterchainGasPaymaster.sol";
// ============ External Imports ============
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
@ -11,7 +13,7 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
* @dev This contract is only intended for paying for messages sent via a specific
* Outbox contract on the same source chain.
*/
contract InterchainGasPaymaster is Ownable {
contract InterchainGasPaymaster is IInterchainGasPaymaster, Ownable {
// ============ Events ============
/**
@ -33,7 +35,7 @@ contract InterchainGasPaymaster is Ownable {
* of a message on its destination chain.
* @param _leafIndex The index of the message in the Outbox merkle tree.
*/
function payGasFor(uint256 _leafIndex) external payable {
function payGasFor(uint256 _leafIndex) external payable override {
emit GasPayment(_leafIndex, msg.value);
}

@ -4,6 +4,7 @@ pragma solidity >=0.6.11;
// ============ Internal Imports ============
import {Outbox} from "./Outbox.sol";
import {Inbox} from "./Inbox.sol";
import {IInterchainGasPaymaster} from "../interfaces/IInterchainGasPaymaster.sol";
import {TypeCasts} from "../libs/TypeCasts.sol";
// ============ External Imports ============
import {ECDSA} from "@openzeppelin/contracts/cryptography/ECDSA.sol";
@ -20,6 +21,11 @@ contract XAppConnectionManager is Ownable {
// Outbox contract
Outbox public outbox;
// Interchain Gas Paymaster contract. The off-chain processor associated with
// the paymaster contract must be willing to process messages dispatched from
// the current Outbox contract, otherwise payments made to the paymaster will
// not result in processed messages.
IInterchainGasPaymaster public interchainGasPaymaster;
// local Inbox address => remote Outbox domain
mapping(address => uint32) public inboxToDomain;
// remote Outbox domain => local Inbox address
@ -33,6 +39,12 @@ contract XAppConnectionManager is Ownable {
*/
event NewOutbox(address indexed outbox);
/**
* @notice Emitted when a new Interchain Gas Paymaster is set.
* @param interchainGasPaymaster The address of the Interchain Gas Paymaster.
*/
event NewInterchainGasPaymaster(address indexed interchainGasPaymaster);
/**
* @notice Emitted when a new Inbox is enrolled / added
* @param domain the remote domain of the Outbox contract for the Inbox
@ -55,12 +67,18 @@ contract XAppConnectionManager is Ownable {
// ============ External Functions ============
/**
* @notice Set the address of the local Outbox contract
* @param _outbox the address of the local Outbox contract
* @notice Sets the address of the local Outbox contract and the address of
* the local Interchain Gas Paymaster contract.
* @dev This should be used to atomically change the local Outbox and Interchain Gas Paymaster.
* @param _outbox The address of the new local Outbox contract.
* @param _interchainGasPaymaster The address of the new local Interchain Gas Paymaster contract.
*/
function setOutbox(address _outbox) external onlyOwner {
outbox = Outbox(_outbox);
emit NewOutbox(_outbox);
function setOutboxAndInterchainGasPaymaster(
address _outbox,
address _interchainGasPaymaster
) external onlyOwner {
setOutbox(_outbox);
setInterchainGasPaymaster(_interchainGasPaymaster);
}
/**
@ -95,6 +113,33 @@ contract XAppConnectionManager is Ownable {
// ============ Public Functions ============
/**
* @notice Sets the address of the local Outbox contract.
* @dev Changing the Outbox and not the Interchain Gas Paymaster may result in
* using an Interchain Gas Paymaster that expects messages to be dispatched via
* a different outbox. Use `setOutboxAndInterchainGasPaymaster` to change both
* atomically.
* @param _outbox The address of the new local Outbox contract.
*/
function setOutbox(address _outbox) public onlyOwner {
outbox = Outbox(_outbox);
emit NewOutbox(_outbox);
}
/**
* @notice Sets the address of the local Interchain Gas Paymaster contract.
* @param _interchainGasPaymaster The address of the new local Interchain Gas Paymaster contract.
*/
function setInterchainGasPaymaster(address _interchainGasPaymaster)
public
onlyOwner
{
interchainGasPaymaster = IInterchainGasPaymaster(
_interchainGasPaymaster
);
emit NewInterchainGasPaymaster(_interchainGasPaymaster);
}
/**
* @notice Check whether _inbox is enrolled
* @param _inbox the inbox to check for enrollment

@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
/**
* @title IInterchainGasPaymaster
* @notice An interface to pay source chain native tokens to cover the gas costs
* of proving & processing messages on destination chains.
* @dev This is only intended for paying for messages sent via a specific
* Outbox contract on the same source chain.
*/
interface IInterchainGasPaymaster {
/**
* @notice Deposits the msg.value as a payment for the proving & processing
* of a message on its destination chain.
* @param _leafIndex The index of the message in the Outbox merkle tree.
*/
function payGasFor(uint256 _leafIndex) external payable;
}

@ -1,13 +1,17 @@
import { ethers } from 'hardhat';
import { expect } from 'chai';
import { ContractTransaction } from 'ethers';
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import {
InterchainGasPaymaster,
InterchainGasPaymaster__factory,
TestOutbox,
TestOutbox__factory,
TestInbox,
TestInbox__factory,
XAppConnectionManager,
XAppConnectionManager__factory,
TestInbox,
} from '../types';
const ONLY_OWNER_REVERT_MSG = 'Ownable: caller is not the owner';
@ -17,10 +21,11 @@ const remoteDomain = 2000;
describe('XAppConnectionManager', async () => {
let connectionManager: XAppConnectionManager,
enrolledInbox: TestInbox,
signer: SignerWithAddress;
signer: SignerWithAddress,
nonOwner: SignerWithAddress;
before(async () => {
[signer] = await ethers.getSigners();
[signer, nonOwner] = await ethers.getSigners();
});
beforeEach(async () => {
@ -48,11 +53,120 @@ describe('XAppConnectionManager', async () => {
expect(await connectionManager!.localDomain()).to.equal(localDomain);
});
it('onlyOwner function rejects call from non-owner', async () => {
const [nonOutbox, nonOwner] = await ethers.getSigners();
await expect(
connectionManager.connect(nonOwner).setOutbox(nonOutbox.address),
).to.be.revertedWith(ONLY_OWNER_REVERT_MSG);
// Used for testing `setOutbox` and `setOutboxAndInterchainGasPaymaster` to avoid
// code duplication
const setOutboxTests = (
setter: (
outbox: string,
signer?: SignerWithAddress,
) => Promise<ContractTransaction>,
) => {
let newOutbox: TestOutbox;
beforeEach(async () => {
const outboxFactory = new TestOutbox__factory(signer);
newOutbox = await outboxFactory.deploy(localDomain);
});
it('Allows owner to set the outbox', async () => {
await setter(newOutbox.address);
expect(await connectionManager.outbox()).to.equal(newOutbox.address);
});
it('Emits the NewOutbox event', async () => {
await expect(setter(newOutbox.address))
.to.emit(connectionManager, 'NewOutbox')
.withArgs(newOutbox.address);
});
it('Reverts a call from non-owner', async () => {
await expect(setter(newOutbox.address, nonOwner)).to.be.revertedWith(
ONLY_OWNER_REVERT_MSG,
);
});
};
// Used for testing `setInterchainGasPaymaster` and `setOutboxAndInterchainGasPaymaster`
// to avoid code duplication
const setInterchainGasPaymasterTests = (
setter: (
interchainGasPaymaster: string,
signer?: SignerWithAddress,
) => Promise<ContractTransaction>,
) => {
let newPaymaster: InterchainGasPaymaster;
beforeEach(async () => {
const paymasterFactory = new InterchainGasPaymaster__factory(signer);
newPaymaster = await paymasterFactory.deploy();
});
it('Allows owner to set the interchainGasPaymaster', async () => {
await setter(newPaymaster.address);
expect(await connectionManager.interchainGasPaymaster()).to.equal(
newPaymaster.address,
);
});
it('Emits the NewInterchainGasPaymaster event', async () => {
await expect(setter(newPaymaster.address))
.to.emit(connectionManager, 'NewInterchainGasPaymaster')
.withArgs(newPaymaster.address);
});
it('Reverts a call from non-owner', async () => {
await expect(setter(newPaymaster.address, nonOwner)).to.be.revertedWith(
ONLY_OWNER_REVERT_MSG,
);
});
};
describe('#setOutboxAndInterchainGasPaymaster', () => {
const dummyAddress = '0xdEADBEeF00000000000000000000000000000000';
// Test the outbox setting works
setOutboxTests((outbox: string, signer?: SignerWithAddress) => {
const connManager = signer
? connectionManager.connect(signer)
: connectionManager;
return connManager.setOutboxAndInterchainGasPaymaster(
outbox,
dummyAddress,
);
});
// And test the interchain gas paymaster setting works
setInterchainGasPaymasterTests(
(interchainGasPaymaster: string, signer?: SignerWithAddress) => {
const connManager = signer
? connectionManager.connect(signer)
: connectionManager;
return connManager.setOutboxAndInterchainGasPaymaster(
dummyAddress,
interchainGasPaymaster,
);
},
);
});
describe('#setOutbox', () => {
setOutboxTests((outbox: string, signer?: SignerWithAddress) => {
const connManager = signer
? connectionManager.connect(signer)
: connectionManager;
return connManager.setOutbox(outbox);
});
});
describe('#setInterchainGasPaymaster', () => {
setInterchainGasPaymasterTests(
(interchainGasPaymaster: string, signer?: SignerWithAddress) => {
const connManager = signer
? connectionManager.connect(signer)
: connectionManager;
return connManager.setInterchainGasPaymaster(interchainGasPaymaster);
},
);
});
it('isInbox returns true for enrolledInbox and false for non-enrolled Inbox', async () => {
@ -62,14 +176,6 @@ describe('XAppConnectionManager', async () => {
.false;
});
it('Allows owner to set the outbox', async () => {
const outboxFactory = new TestOutbox__factory(signer);
const newOutbox = await outboxFactory.deploy(localDomain);
await connectionManager.setOutbox(newOutbox.address);
expect(await connectionManager.outbox()).to.equal(newOutbox.address);
});
it('Owner can enroll a inbox', async () => {
const newRemoteDomain = 3000;
const inboxFactory = new TestInbox__factory(signer);

Loading…
Cancel
Save