Commit d37dc769 authored by Kelvin Fichter's avatar Kelvin Fichter Committed by GitHub

Add success check to message passing contracts (#18)

* Added fix for error in StateTransitioner

* Added tests for some precompiles

* Cleaned build and added typechain

* Linted files

* Added success check to message passing contracts

* Audit fix/message passer storage (#19)

* Made message passer use storage

* Fixed message hashing
parent a4340e51
...@@ -14,7 +14,8 @@ contract OVM_BaseCrossDomainMessenger is iOVM_BaseCrossDomainMessenger { ...@@ -14,7 +14,8 @@ contract OVM_BaseCrossDomainMessenger is iOVM_BaseCrossDomainMessenger {
* Contract Variables * * Contract Variables *
**********************/ **********************/
mapping (bytes32 => bool) public receivedMessages; mapping (bytes32 => bool) public relayedMessages;
mapping (bytes32 => bool) public successfulMessages;
mapping (bytes32 => bool) public sentMessages; mapping (bytes32 => bool) public sentMessages;
uint256 public messageNonce; uint256 public messageNonce;
address public xDomainMessageSender; address public xDomainMessageSender;
......
...@@ -6,7 +6,6 @@ pragma experimental ABIEncoderV2; ...@@ -6,7 +6,6 @@ pragma experimental ABIEncoderV2;
import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol"; import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol";
import { Lib_AddressResolver } from "../../libraries/resolver/Lib_AddressResolver.sol"; import { Lib_AddressResolver } from "../../libraries/resolver/Lib_AddressResolver.sol";
import { Lib_SecureMerkleTrie } from "../../libraries/trie/Lib_SecureMerkleTrie.sol"; import { Lib_SecureMerkleTrie } from "../../libraries/trie/Lib_SecureMerkleTrie.sol";
import { Lib_BytesUtils } from "../../libraries/utils/Lib_BytesUtils.sol";
/* Interface Imports */ /* Interface Imports */
import { iOVM_L1CrossDomainMessenger } from "../../iOVM/bridge/iOVM_L1CrossDomainMessenger.sol"; import { iOVM_L1CrossDomainMessenger } from "../../iOVM/bridge/iOVM_L1CrossDomainMessenger.sol";
...@@ -83,19 +82,29 @@ contract OVM_L1CrossDomainMessenger is iOVM_L1CrossDomainMessenger, OVM_BaseCros ...@@ -83,19 +82,29 @@ contract OVM_L1CrossDomainMessenger is iOVM_L1CrossDomainMessenger, OVM_BaseCros
); );
require( require(
receivedMessages[keccak256(xDomainCalldata)] == false, successfulMessages[keccak256(xDomainCalldata)] == false,
"Provided message has already been received." "Provided message has already been received."
); );
xDomainMessageSender = _sender; xDomainMessageSender = _sender;
_target.call(_message); (bool success, ) = _target.call(_message);
// Messages are considered successfully executed if they complete // Mark the message as received if the call was successful. Ensures that a message can be
// without running out of gas (revert or not). As a result, we can // relayed multiple times in the case that the call reverted.
// ignore the result of the call and always mark the message as if (success == true) {
// successfully executed because we won't get here unless we have successfulMessages[keccak256(xDomainCalldata)] = true;
// enough gas left over. }
receivedMessages[keccak256(xDomainCalldata)] = true;
// Store an identifier that can be used to prove that the given message was relayed by some
// user. Gives us an easy way to pay relayers for their work.
bytes32 relayId = keccak256(
abi.encodePacked(
xDomainCalldata,
msg.sender,
block.number
)
);
relayedMessages[relayId] = true;
} }
/** /**
...@@ -189,15 +198,20 @@ contract OVM_L1CrossDomainMessenger is iOVM_L1CrossDomainMessenger, OVM_BaseCros ...@@ -189,15 +198,20 @@ contract OVM_L1CrossDomainMessenger is iOVM_L1CrossDomainMessenger, OVM_BaseCros
L2MessageInclusionProof memory _proof L2MessageInclusionProof memory _proof
) )
internal internal
pure view
returns ( returns (
bool bool
) )
{ {
bytes32 storageKey = keccak256( bytes32 storageKey = keccak256(
Lib_BytesUtils.concat( abi.encodePacked(
abi.encodePacked(keccak256(_xDomainCalldata)), keccak256(
abi.encodePacked(uint256(0)) abi.encodePacked(
_xDomainCalldata,
resolve("OVM_L2CrossDomainMessenger")
)
),
uint256(0)
) )
); );
......
// SPDX-License-Identifier: UNLICENSED // SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0; pragma solidity >0.6.0 <0.8.0;
pragma experimental ABIEncoderV2; pragma experimental ABIEncoderV2;
/* Library Imports */ /* Library Imports */
...@@ -18,6 +18,7 @@ import { console } from "@nomiclabs/buidler/console.sol"; ...@@ -18,6 +18,7 @@ import { console } from "@nomiclabs/buidler/console.sol";
/** /**
* @title OVM_L2CrossDomainMessenger * @title OVM_L2CrossDomainMessenger
* @dev L2 CONTRACT (COMPILED)
*/ */
contract OVM_L2CrossDomainMessenger is iOVM_L2CrossDomainMessenger, OVM_BaseCrossDomainMessenger, Lib_AddressResolver { contract OVM_L2CrossDomainMessenger is iOVM_L2CrossDomainMessenger, OVM_BaseCrossDomainMessenger, Lib_AddressResolver {
...@@ -76,19 +77,29 @@ contract OVM_L2CrossDomainMessenger is iOVM_L2CrossDomainMessenger, OVM_BaseCros ...@@ -76,19 +77,29 @@ contract OVM_L2CrossDomainMessenger is iOVM_L2CrossDomainMessenger, OVM_BaseCros
); );
require( require(
receivedMessages[keccak256(xDomainCalldata)] == false, successfulMessages[keccak256(xDomainCalldata)] == false,
"Provided message has already been received." "Provided message has already been received."
); );
xDomainMessageSender = _sender; xDomainMessageSender = _sender;
_target.call(_message); (bool success, ) = _target.call(_message);
// Messages are considered successfully executed if they complete // Mark the message as received if the call was successful. Ensures that a message can be
// without running out of gas (revert or not). As a result, we can // relayed multiple times in the case that the call reverted.
// ignore the result of the call and always mark the message as if (success == true) {
// successfully executed because we won't get here unless we have successfulMessages[keccak256(xDomainCalldata)] = true;
// enough gas left over. }
receivedMessages[keccak256(xDomainCalldata)] = true;
// Store an identifier that can be used to prove that the given message was relayed by some
// user. Gives us an easy way to pay relayers for their work.
bytes32 relayId = keccak256(
abi.encodePacked(
xDomainCalldata,
msg.sender,
block.number
)
);
relayedMessages[relayId] = true;
} }
......
...@@ -10,6 +10,7 @@ import { iOVM_ExecutionManager } from "../../iOVM/execution/iOVM_ExecutionManage ...@@ -10,6 +10,7 @@ import { iOVM_ExecutionManager } from "../../iOVM/execution/iOVM_ExecutionManage
/** /**
* @title OVM_DeployerWhitelist * @title OVM_DeployerWhitelist
* @dev L2 CONTRACT (NOT COMPILED)
*/ */
contract OVM_DeployerWhitelist is iOVM_DeployerWhitelist { contract OVM_DeployerWhitelist is iOVM_DeployerWhitelist {
......
...@@ -7,6 +7,7 @@ import { iOVM_ExecutionManager } from "../../iOVM/execution/iOVM_ExecutionManage ...@@ -7,6 +7,7 @@ import { iOVM_ExecutionManager } from "../../iOVM/execution/iOVM_ExecutionManage
/** /**
* @title OVM_L1MessageSender * @title OVM_L1MessageSender
* @dev L2 CONTRACT (NOT COMPILED)
*/ */
contract OVM_L1MessageSender is iOVM_L1MessageSender { contract OVM_L1MessageSender is iOVM_L1MessageSender {
......
// SPDX-License-Identifier: UNLICENSED // SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0; pragma solidity >0.6.0 <0.8.0;
/* Interface Imports */ /* Interface Imports */
import { iOVM_L2ToL1MessagePasser } from "../../iOVM/precompiles/iOVM_L2ToL1MessagePasser.sol"; import { iOVM_L2ToL1MessagePasser } from "../../iOVM/precompiles/iOVM_L2ToL1MessagePasser.sol";
...@@ -7,6 +7,7 @@ import { iOVM_ExecutionManager } from "../../iOVM/execution/iOVM_ExecutionManage ...@@ -7,6 +7,7 @@ import { iOVM_ExecutionManager } from "../../iOVM/execution/iOVM_ExecutionManage
/** /**
* @title OVM_L2ToL1MessagePasser * @title OVM_L2ToL1MessagePasser
* @dev L2 CONTRACT (COMPILED)
*/ */
contract OVM_L2ToL1MessagePasser is iOVM_L2ToL1MessagePasser { contract OVM_L2ToL1MessagePasser is iOVM_L2ToL1MessagePasser {
...@@ -14,7 +15,7 @@ contract OVM_L2ToL1MessagePasser is iOVM_L2ToL1MessagePasser { ...@@ -14,7 +15,7 @@ contract OVM_L2ToL1MessagePasser is iOVM_L2ToL1MessagePasser {
* Contract Variables * * Contract Variables *
**********************/ **********************/
uint256 internal nonce; mapping (bytes32 => bool) public sentMessages;
/******************** /********************
...@@ -31,14 +32,11 @@ contract OVM_L2ToL1MessagePasser is iOVM_L2ToL1MessagePasser { ...@@ -31,14 +32,11 @@ contract OVM_L2ToL1MessagePasser is iOVM_L2ToL1MessagePasser {
override override
public public
{ {
// For now, to be trustfully relayed by sequencer to L1, so just emit sentMessages[keccak256(
// an event for the sequencer to pick up. abi.encodePacked(
emit L2ToL1Message( _message,
nonce, msg.sender
iOVM_ExecutionManager(msg.sender).ovmCALLER(), )
_message )] = true;
);
nonce = nonce + 1;
} }
} }
...@@ -17,6 +17,8 @@ import { ...@@ -17,6 +17,8 @@ import {
DUMMY_BATCH_PROOFS, DUMMY_BATCH_PROOFS,
TrieTestGenerator, TrieTestGenerator,
toHexString, toHexString,
getNextBlockNumber,
remove0x,
} from '../../../helpers' } from '../../../helpers'
import { getContractInterface } from '../../../../src' import { getContractInterface } from '../../../../src'
import { keccak256 } from 'ethers/lib/utils' import { keccak256 } from 'ethers/lib/utils'
...@@ -157,6 +159,7 @@ describe('OVM_L1CrossDomainMessenger', () => { ...@@ -157,6 +159,7 @@ describe('OVM_L1CrossDomainMessenger', () => {
let message: string let message: string
let sender: string let sender: string
let proof: any let proof: any
let calldata: string
before(async () => { before(async () => {
target = Mock__TargetContract.address target = Mock__TargetContract.address
message = Mock__TargetContract.interface.encodeFunctionData('setTarget', [ message = Mock__TargetContract.interface.encodeFunctionData('setTarget', [
...@@ -164,11 +167,15 @@ describe('OVM_L1CrossDomainMessenger', () => { ...@@ -164,11 +167,15 @@ describe('OVM_L1CrossDomainMessenger', () => {
]) ])
sender = await signer.getAddress() sender = await signer.getAddress()
const calldata = getXDomainCalldata(sender, target, message, 0) calldata = getXDomainCalldata(sender, target, message, 0)
const precompile = '0x4200000000000000000000000000000000000000' const precompile = '0x4200000000000000000000000000000000000000'
const storageKey = keccak256(keccak256(calldata) + '00'.repeat(32)) const storageKey = keccak256(
keccak256(
calldata + remove0x(Mock__OVM_L2CrossDomainMessenger.address)
) + '00'.repeat(32)
)
const storageGenerator = await TrieTestGenerator.fromNodes({ const storageGenerator = await TrieTestGenerator.fromNodes({
nodes: [ nodes: [
{ {
...@@ -279,7 +286,9 @@ describe('OVM_L1CrossDomainMessenger', () => { ...@@ -279,7 +286,9 @@ describe('OVM_L1CrossDomainMessenger', () => {
).to.be.reverted ).to.be.reverted
}) })
it('should send a call to the target contract', async () => { it('should send a successful call to the target contract', async () => {
const blockNumber = await getNextBlockNumber(ethers.provider)
await OVM_L1CrossDomainMessenger.relayMessage( await OVM_L1CrossDomainMessenger.relayMessage(
target, target,
sender, sender,
...@@ -288,9 +297,22 @@ describe('OVM_L1CrossDomainMessenger', () => { ...@@ -288,9 +297,22 @@ describe('OVM_L1CrossDomainMessenger', () => {
proof proof
) )
expect(Mock__TargetContract.smocked.setTarget.calls[0]).to.deep.equal([ expect(
NON_ZERO_ADDRESS, await OVM_L1CrossDomainMessenger.successfulMessages(keccak256(calldata))
]) ).to.equal(true)
expect(
await OVM_L1CrossDomainMessenger.relayedMessages(
keccak256(
calldata +
remove0x(await signer.getAddress()) +
remove0x(BigNumber.from(blockNumber).toHexString()).padStart(
64,
'0'
)
)
)
).to.equal(true)
}) })
it('should revert if trying to send the same message twice', async () => { it('should revert if trying to send the same message twice', async () => {
......
...@@ -5,6 +5,8 @@ import { ethers } from '@nomiclabs/buidler' ...@@ -5,6 +5,8 @@ import { ethers } from '@nomiclabs/buidler'
import { ContractFactory, Contract } from 'ethers' import { ContractFactory, Contract } from 'ethers'
import { MockContract, smockit } from '@eth-optimism/smock' import { MockContract, smockit } from '@eth-optimism/smock'
import { NON_ZERO_ADDRESS } from '../../../helpers/constants' import { NON_ZERO_ADDRESS } from '../../../helpers/constants'
import { keccak256 } from 'ethers/lib/utils'
import { remove0x } from '../../../helpers'
const ELEMENT_TEST_SIZES = [1, 2, 4, 8, 16] const ELEMENT_TEST_SIZES = [1, 2, 4, 8, 16]
...@@ -61,16 +63,18 @@ describe('OVM_L2ToL1MessagePasser', () => { ...@@ -61,16 +63,18 @@ describe('OVM_L2ToL1MessagePasser', () => {
for (let i = 0; i < size; i++) { for (let i = 0; i < size; i++) {
const message = '0x' + '12' + '34'.repeat(i) const message = '0x' + '12' + '34'.repeat(i)
await expect( await callPrecompile(
callPrecompile(
Helper_PrecompileCaller, Helper_PrecompileCaller,
OVM_L2ToL1MessagePasser, OVM_L2ToL1MessagePasser,
'passMessageToL1', 'passMessageToL1',
[message] [message]
) )
expect(
await OVM_L2ToL1MessagePasser.sentMessages(
keccak256(message + remove0x(Helper_PrecompileCaller.address))
) )
.to.emit(OVM_L2ToL1MessagePasser, 'L2ToL1Message') ).to.equal(true)
.withArgs(i, NON_ZERO_ADDRESS, message)
} }
}) })
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment