Skip to content
Open
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
8 changes: 3 additions & 5 deletions contracts/crosschain/ERC7786OpenBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,9 @@ contract ERC7786OpenBridge is IERC7786GatewaySource, IERC7786Recipient, Ownable,
address gateway = _gateways.at(i);
// send message
bytes32 id = IERC7786GatewaySource(gateway).sendMessage(bridge, wrappedPayload, attributes);
// if ID, track it
if (id != bytes32(0)) {
outbox[i] = Outbox(gateway, id);
needsId = true;
}
// fill outbox
outbox[i] = Outbox(gateway, id);
needsId = needsId || id != bytes32(0);
}

if (needsId) {
Expand Down
10 changes: 8 additions & 2 deletions contracts/mocks/crosschain/ERC7786GatewayMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ contract ERC7786GatewayMock is IERC7786GatewaySource {
using BitMaps for BitMaps.BitMap;
using InteroperableAddress for *;

bytes32 public sendId;

function setSendId(bytes32 _sendId) public {
sendId = _sendId;
}

function supportsAttribute(bytes4 /*selector*/) public pure returns (bool) {
return false;
}
Expand All @@ -33,7 +39,7 @@ contract ERC7786GatewayMock is IERC7786GatewaySource {
"Receiver error"
);

emit MessageSent(0, sender, recipient, payload, 0, attributes);
return 0;
emit MessageSent(sendId, sender, recipient, payload, 0, attributes);
return sendId;
}
}
80 changes: 56 additions & 24 deletions test/crosschain/ERC7786OpenBridge.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ const { ethers } = require('hardhat');
const { expect } = require('chai');
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
const { anyValue } = require('@nomicfoundation/hardhat-chai-matchers/withArgs');

const AxelarHelper = require('./axelar/AxelarHelper');
const { getLocalChain } = require('@openzeppelin/contracts/test/helpers/chains');

const getAddress = account => ethers.getAddress(account.target ?? account.address ?? account);

Expand All @@ -13,24 +12,15 @@ const M = 5;
async function fixture() {
const [owner, sender, ...accounts] = await ethers.getSigners();

const chain = await getLocalChain();
const protocoles = await Promise.all(
Array(M)
.fill()
.map(() => AxelarHelper.deploy(owner)),
.map(() => ethers.deployContract('ERC7786GatewayMock')),
);

const { chain } = protocoles.at(0);

const bridgeA = await ethers.deployContract('ERC7786OpenBridge', [
owner,
protocoles.map(({ gatewayA }) => gatewayA),
N,
]);
const bridgeB = await ethers.deployContract('ERC7786OpenBridge', [
owner,
protocoles.map(({ gatewayB }) => gatewayB),
N,
]);
const bridgeA = await ethers.deployContract('ERC7786OpenBridge', [owner, protocoles, N]);
const bridgeB = await ethers.deployContract('ERC7786OpenBridge', [owner, protocoles, N]);
await bridgeA.registerRemoteBridge(chain.toErc7930(bridgeB));
await bridgeB.registerRemoteBridge(chain.toErc7930(bridgeA));

Expand All @@ -43,17 +33,13 @@ describe('ERC7786OpenBridge', function () {
});

it('initial setup', async function () {
await expect(this.bridgeA.getGateways()).to.eventually.deep.equal(
this.protocoles.map(({ gatewayA }) => getAddress(gatewayA)),
);
await expect(this.bridgeA.getGateways()).to.eventually.deep.equal(this.protocoles.map(getAddress));
await expect(this.bridgeA.getThreshold()).to.eventually.equal(N);
await expect(this.bridgeA.getRemoteBridge(this.chain.erc7930)).to.eventually.equal(
this.chain.toErc7930(this.bridgeB),
);

await expect(this.bridgeB.getGateways()).to.eventually.deep.equal(
this.protocoles.map(({ gatewayB }) => getAddress(gatewayB)),
);
await expect(this.bridgeB.getGateways()).to.eventually.deep.equal(this.protocoles.map(getAddress));
await expect(this.bridgeB.getThreshold()).to.eventually.equal(N);
await expect(this.bridgeB.getRemoteBridge(this.chain.erc7930)).to.eventually.equal(
this.chain.toErc7930(this.bridgeA),
Expand Down Expand Up @@ -136,9 +122,9 @@ describe('ERC7786OpenBridge', function () {
);

// MessagePosted to all gateways on the A side and received from all gateways on the B side
for (const { gatewayA, gatewayB } of this.protocoles) {
for (const gateway of this.protocoles) {
await expect(txPromise)
.to.emit(gatewayA, 'MessageSent')
.to.emit(gateway, 'MessageSent')
.withArgs(
ethers.ZeroHash,
this.chain.toErc7930(this.bridgeA),
Expand All @@ -148,7 +134,7 @@ describe('ERC7786OpenBridge', function () {
anyValue,
)
.to.emit(this.bridgeB, 'Received')
.withArgs(resultId, gatewayB);
.withArgs(resultId, gateway);
}

if (this.outcome) {
Expand All @@ -175,4 +161,50 @@ describe('ERC7786OpenBridge', function () {
}
});
});

describe('outbox tracking', function () {
it('records every gateway in the outbox and emits OutboxDetails when a gateway returns a non-zero id', async function () {
const destination = await ethers.deployContract('$ERC7786RecipientMock', [this.bridgeB]);
const payload = ethers.randomBytes(128);
const attributes = [];

const outbox = this.protocoles.map(gateway => [gateway.target, ethers.ZeroHash]);
const id1 = (outbox[0][1] = ethers.hexlify(ethers.randomBytes(32)));
const id2 = (outbox[2][1] = ethers.hexlify(ethers.randomBytes(32)));

await this.protocoles[0].setSendId(id1);
await this.protocoles[2].setSendId(id2);

// The outbox lists all gateways, including the one that returned a zero id (with its address, not address(0))
const sendId = ethers.keccak256(ethers.AbiCoder.defaultAbiCoder().encode(['(address,bytes32)[]'], [outbox]));

await expect(
this.bridgeA.connect(this.sender).sendMessage(this.chain.toErc7930(destination), payload, attributes),
)
.to.emit(this.bridgeA, 'MessageSent')
.withArgs(sendId, this.chain.toErc7930(this.sender), this.chain.toErc7930(destination), payload, 0n, [])
.to.emit(this.bridgeA, 'OutboxDetails')
.withArgs(sendId, outbox);
});

it('does not emit OutboxDetails and keeps a zero sendId when all gateways return zero', async function () {
const destination = await ethers.deployContract('$ERC7786RecipientMock', [this.bridgeB]);
const payload = ethers.randomBytes(128);
const attributes = [];

await expect(
this.bridgeA.connect(this.sender).sendMessage(this.chain.toErc7930(destination), payload, attributes),
)
.to.emit(this.bridgeA, 'MessageSent')
.withArgs(
ethers.ZeroHash,
this.chain.toErc7930(this.sender),
this.chain.toErc7930(destination),
payload,
0n,
[],
)
.to.not.emit(this.bridgeA, 'OutboxDetails');
});
});
});
Loading