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

fix(protocol): check no loops in multi-hop in Bridge #16659

Merged
merged 8 commits into from
Apr 5, 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
30 changes: 27 additions & 3 deletions packages/protocol/contracts/signal/SignalService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ contract SignalService is EssentialContract, ISignalService {
uint256[48] private __gap;

error SS_EMPTY_PROOF();
error SS_INVALID_HOPS_WITH_LOOP();
error SS_INVALID_SENDER();
error SS_INVALID_LAST_HOP_CHAINID();
error SS_INVALID_MID_HOP_CHAINID();
Expand Down Expand Up @@ -92,6 +93,11 @@ contract SignalService is EssentialContract, ISignalService {
{
HopProof[] memory hopProofs = abi.decode(_proof, (HopProof[]));
if (hopProofs.length == 0) revert SS_EMPTY_PROOF();
uint256 lenLessOne;
unchecked {
lenLessOne = hopProofs.length - 1;
}
uint64[] memory trace = new uint64[](lenLessOne);

uint64 chainId = _chainId;
address app = _app;
Expand All @@ -103,13 +109,19 @@ contract SignalService is EssentialContract, ISignalService {
for (uint256 i; i < hopProofs.length; ++i) {
hop = hopProofs[i];

for (uint256 j; j < i; ++j) {
if (trace[j] == hop.chainId) revert SS_INVALID_HOPS_WITH_LOOP();
}

bytes32 signalRoot = _verifyHopProof(chainId, app, signal, value, hop, signalService);
bool isLastHop = i == hopProofs.length - 1;
bool isLastHop = i == lenLessOne;

if (isLastHop) {
if (hop.chainId != block.chainid) revert SS_INVALID_LAST_HOP_CHAINID();
signalService = address(this);
} else {
trace[i] = hop.chainId;

if (hop.chainId == 0 || hop.chainId == block.chainid) {
revert SS_INVALID_MID_HOP_CHAINID();
}
Expand Down Expand Up @@ -148,23 +160,35 @@ contract SignalService is EssentialContract, ISignalService {
HopProof[] memory hopProofs = abi.decode(_proof, (HopProof[]));
if (hopProofs.length == 0) revert SS_EMPTY_PROOF();

uint256 lenLessOne;
unchecked {
lenLessOne = hopProofs.length - 1;
}
uint64[] memory trace = new uint64[](lenLessOne);

dantaik marked this conversation as resolved.
Show resolved Hide resolved
uint64 chainId = _chainId;
address app = _app;
bytes32 signal = _signal;
bytes32 value = _signal;
address signalService = resolve(chainId, "signal_service", false);

HopProof memory hop;

for (uint256 i; i < hopProofs.length; ++i) {
hop = hopProofs[i];

for (uint256 j; j < i; ++j) {
if (trace[j] == hop.chainId) revert SS_INVALID_HOPS_WITH_LOOP();
}

_verifyHopProof(chainId, app, signal, value, hop, signalService);
bool isLastHop = i == hopProofs.length - 1;

if (isLastHop) {
if (i == lenLessOne) {
if (hop.chainId != block.chainid) revert SS_INVALID_LAST_HOP_CHAINID();
signalService = address(this);
} else {
trace[i] = hop.chainId;

if (hop.chainId == 0 || hop.chainId == block.chainid) {
revert SS_INVALID_MID_HOP_CHAINID();
}
Expand Down
49 changes: 49 additions & 0 deletions packages/protocol/test/signal/SignalService.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,55 @@ contract TestSignalService is TaikoTest {
});
}

function test_SignalService_proveSignalReceived_revert_with_a_loop() public {
uint64 srcChainId = uint64(block.chainid + 1);

vm.prank(Alice);
addressManager.setAddress(srcChainId, "signal_service", randAddress());

SignalService.HopProof[] memory proofs = new SignalService.HopProof[](3);

// first hop with full merkle proof
proofs[0].chainId = uint64(block.chainid + 2);
proofs[0].blockId = 1;
proofs[0].rootHash = randBytes32();
proofs[0].accountProof = new bytes[](1);
proofs[0].storageProof = new bytes[](10);

// second hop with storage merkle proof
proofs[1].chainId = proofs[0].chainId; // same
proofs[1].blockId = 2;
proofs[1].rootHash = randBytes32();
proofs[1].accountProof = new bytes[](0);
proofs[1].storageProof = new bytes[](10);

// third/last hop with full merkle proof
proofs[2].chainId = uint64(block.chainid);
proofs[2].blockId = 3;
proofs[2].rootHash = randBytes32();
proofs[2].accountProof = new bytes[](1);
proofs[2].storageProof = new bytes[](10);

// Add two trusted hop relayers
vm.startPrank(Alice);
addressManager.setAddress(proofs[0].chainId, "signal_service", randAddress() /*relay1*/ );
addressManager.setAddress(proofs[1].chainId, "signal_service", randAddress() /*relay2*/ );
vm.stopPrank();

vm.prank(taiko);
signalService.syncChainData(
proofs[1].chainId, LibSignals.STATE_ROOT, proofs[2].blockId, proofs[2].rootHash
);

vm.expectRevert(SignalService.SS_INVALID_HOPS_WITH_LOOP.selector);
signalService.proveSignalReceived({
_chainId: srcChainId,
_app: randAddress(),
_signal: randBytes32(),
_proof: abi.encode(proofs)
});
}

function test_SignalService_proveSignalReceived_multiple_hops_caching() public {
uint64 srcChainId = uint64(block.chainid + 1);
uint64 nextChainId = srcChainId + 100;
Expand Down
Loading