Merge Inbox prove and process (#339)

* Merge Inbox prove and process

* PR reviews

* Package-lock
pull/354/head
Nam Chu Hoai 3 years ago committed by GitHub
parent b5f121b837
commit 0216d28dcf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 48
      package-lock.json
  2. 38
      rust/abacus-base/src/inbox.rs
  3. 4
      rust/abacus-core/src/traits/common.rs
  4. 10
      rust/abacus-core/src/traits/inbox.rs
  5. 16
      rust/abacus-test/src/mocks/inbox.rs
  6. 2
      rust/agents/relayer/src/checkpoint_relayer.rs
  7. 45
      rust/agents/relayer/src/message_processor.rs
  8. 43
      rust/chains/abacus-ethereum/abis/Inbox.abi.json
  9. 39
      rust/chains/abacus-ethereum/src/inbox.rs
  10. 8
      rust/config/test/alfajores_config.json
  11. 8
      rust/config/test/fuji_config.json
  12. 8
      rust/config/test/kovan_config.json
  13. 8
      rust/config/test/mumbai_config.json
  14. 3
      rust/tools/abacus-cli/src/subcommands/prove.rs
  15. 95
      solidity/core/contracts/Inbox.sol
  16. 12
      solidity/core/contracts/test/TestInbox.sol
  17. 7
      solidity/core/interfaces/IInbox.sol
  18. 175
      solidity/core/test/inbox.test.ts
  19. 2
      typescript/hardhat/package.json
  20. 1
      typescript/hardhat/src/TestAbacusDeploy.ts
  21. 1
      typescript/utils/src/types.ts
  22. 40
      vectors/messageWithProof.json

48
package-lock.json generated

@ -25142,7 +25142,7 @@
"version": "0.0.18",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@abacus-network/core": "^0.0.5",
"@abacus-network/core": "^0.0.6",
"@abacus-network/utils": "^0.0.7",
"@nomiclabs/hardhat-ethers": "^2.0.5",
"@nomiclabs/hardhat-waffle": "^2.0.2",
@ -25156,27 +25156,6 @@
"typescript": "^4.3.2"
}
},
"typescript/hardhat/node_modules/@abacus-network/core": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/@abacus-network/core/-/core-0.0.5.tgz",
"integrity": "sha512-DKDqnGGfsgKrNzZvc9ewD8DS+4yVs+ugpGZtlAjlOzwpZ97Rk61RjrSBO0p4g85UtVg5GI0CLqfzgYdWSS81eQ==",
"dependencies": {
"@abacus-network/utils": "^0.0.5",
"@openzeppelin/contracts": "^3.4.2",
"@openzeppelin/contracts-upgradeable": "~3.4.2",
"@summa-tx/memview-sol": "^2.0.0",
"ts-generator": "^0.1.1"
}
},
"typescript/hardhat/node_modules/@abacus-network/core/node_modules/@abacus-network/utils": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/@abacus-network/utils/-/utils-0.0.5.tgz",
"integrity": "sha512-B8zS1RlKh8Pu7hifeenbAtPg2PRubgcPx3MyJz0TE8ryyDDOd54Xl9g96Xx9mrF7xeLRXBkACsi8S6DkgK+GOw==",
"dependencies": {
"chai": "^4.3.0",
"ethers": "^5.4.7"
}
},
"typescript/hardhat/node_modules/@abacus-network/utils": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/@abacus-network/utils/-/utils-0.0.7.tgz",
@ -25721,7 +25700,7 @@
"@abacus-network/hardhat": {
"version": "file:typescript/hardhat",
"requires": {
"@abacus-network/core": "^0.0.5",
"@abacus-network/core": "^0.0.6",
"@abacus-network/utils": "^0.0.7",
"@nomiclabs/hardhat-ethers": "^2.0.5",
"@nomiclabs/hardhat-waffle": "^2.0.2",
@ -25735,29 +25714,6 @@
"typescript": "^4.3.2"
},
"dependencies": {
"@abacus-network/core": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/@abacus-network/core/-/core-0.0.5.tgz",
"integrity": "sha512-DKDqnGGfsgKrNzZvc9ewD8DS+4yVs+ugpGZtlAjlOzwpZ97Rk61RjrSBO0p4g85UtVg5GI0CLqfzgYdWSS81eQ==",
"requires": {
"@abacus-network/utils": "^0.0.5",
"@openzeppelin/contracts": "^3.4.2",
"@openzeppelin/contracts-upgradeable": "~3.4.2",
"@summa-tx/memview-sol": "^2.0.0",
"ts-generator": "^0.1.1"
},
"dependencies": {
"@abacus-network/utils": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/@abacus-network/utils/-/utils-0.0.5.tgz",
"integrity": "sha512-B8zS1RlKh8Pu7hifeenbAtPg2PRubgcPx3MyJz0TE8ryyDDOd54Xl9g96Xx9mrF7xeLRXBkACsi8S6DkgK+GOw==",
"requires": {
"chai": "^4.3.0",
"ethers": "^5.4.7"
}
}
}
},
"@abacus-network/utils": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/@abacus-network/utils/-/utils-0.0.7.tgz",

@ -79,21 +79,13 @@ impl Inbox for CachingInbox {
self.inbox.remote_domain().await
}
async fn prove(&self, proof: &Proof) -> Result<TxOutcome, ChainCommunicationError> {
self.inbox.prove(proof).await
}
async fn process(&self, message: &AbacusMessage) -> Result<TxOutcome, ChainCommunicationError> {
self.inbox.process(message).await
}
/// Prove a leaf in the inbox and then process its message
async fn prove_and_process(
/// Process a message
async fn process(
&self,
message: &AbacusMessage,
proof: &Proof,
) -> Result<TxOutcome, ChainCommunicationError> {
self.inbox.prove_and_process(message, proof).await
self.inbox.process(message, proof).await
}
async fn message_status(&self, leaf: H256) -> Result<MessageStatus, ChainCommunicationError> {
@ -210,22 +202,6 @@ impl Inbox for InboxVariants {
}
}
async fn prove(&self, proof: &Proof) -> Result<TxOutcome, ChainCommunicationError> {
match self {
InboxVariants::Ethereum(inbox) => inbox.prove(proof).await,
InboxVariants::Mock(mock_inbox) => mock_inbox.prove(proof).await,
InboxVariants::Other(inbox) => inbox.prove(proof).await,
}
}
async fn process(&self, message: &AbacusMessage) -> Result<TxOutcome, ChainCommunicationError> {
match self {
InboxVariants::Ethereum(inbox) => inbox.process(message).await,
InboxVariants::Mock(mock_inbox) => mock_inbox.process(message).await,
InboxVariants::Other(inbox) => inbox.process(message).await,
}
}
async fn message_status(&self, leaf: H256) -> Result<MessageStatus, ChainCommunicationError> {
match self {
InboxVariants::Ethereum(inbox) => inbox.message_status(leaf).await,
@ -234,15 +210,15 @@ impl Inbox for InboxVariants {
}
}
async fn prove_and_process(
async fn process(
&self,
message: &AbacusMessage,
proof: &Proof,
) -> Result<TxOutcome, ChainCommunicationError> {
match self {
InboxVariants::Ethereum(inbox) => inbox.prove_and_process(message, proof).await,
InboxVariants::Mock(mock_inbox) => mock_inbox.prove_and_process(message, proof).await,
InboxVariants::Other(inbox) => inbox.prove_and_process(message, proof).await,
InboxVariants::Ethereum(inbox) => inbox.process(message, proof).await,
InboxVariants::Mock(mock_inbox) => mock_inbox.process(message, proof).await,
InboxVariants::Other(inbox) => inbox.process(message, proof).await,
}
}
}

@ -12,8 +12,6 @@ pub enum State {
pub enum MessageStatus {
/// Message is unknown
None = 0,
/// Message has been proven but not processed
Proven = 1,
/// Message has been processed
Processed = 2,
Processed = 1,
}

@ -14,14 +14,8 @@ pub trait Inbox: AbacusCommon + Send + Sync + std::fmt::Debug {
/// Return the domain of the inbox's linked outbox
async fn remote_domain(&self) -> Result<u32, ChainCommunicationError>;
/// Dispatch a transaction to prove inclusion of some leaf in the inbox.
async fn prove(&self, proof: &Proof) -> Result<TxOutcome, ChainCommunicationError>;
/// Trigger processing of a message
async fn process(&self, message: &AbacusMessage) -> Result<TxOutcome, ChainCommunicationError>;
/// Prove a leaf in the inbox and then process its message
async fn prove_and_process(
/// Process a message
async fn process(
&self,
message: &AbacusMessage,
proof: &Proof,

@ -16,9 +16,7 @@ mock! {
pub fn _prove(&self, proof: &Proof) -> Result<TxOutcome, ChainCommunicationError> {}
pub fn _process(&self, message: &AbacusMessage) -> Result<TxOutcome, ChainCommunicationError> {}
pub fn _prove_and_process(
pub fn _process(
&self,
message: &AbacusMessage,
proof: &Proof,
@ -56,20 +54,12 @@ impl Inbox for MockInboxContract {
self._remote_domain()
}
async fn prove(&self, proof: &Proof) -> Result<TxOutcome, ChainCommunicationError> {
self._prove(proof)
}
async fn process(&self, message: &AbacusMessage) -> Result<TxOutcome, ChainCommunicationError> {
self._process(message)
}
async fn prove_and_process(
async fn process(
&self,
message: &AbacusMessage,
proof: &Proof,
) -> Result<TxOutcome, ChainCommunicationError> {
self._prove_and_process(message, proof)
self._process(message, proof)
}
async fn message_status(&self, leaf: H256) -> Result<MessageStatus, ChainCommunicationError> {

@ -106,7 +106,7 @@ impl CheckpointRelayer {
match self
.inbox_contracts
.inbox
.prove_and_process(&message.message, &proof)
.process(&message.message, &proof)
.await
{
Ok(outcome) => {

@ -91,44 +91,27 @@ impl MessageProcessor {
}
match self.prover_sync.get_proof(message_leaf_index) {
Ok(proof) => {
match self.inbox.prove_and_process(&message.message, &proof).await {
Ok(outcome) => {
info!(
leaf_index = message_leaf_index,
hash = ?outcome.txid,
"[MessageProcessor] processed"
);
self.db.mark_leaf_as_processed(message_leaf_index)?;
Ok(MessageProcessingStatus::Processed)
}
Err(err) => {
error!(leaf_index = message_leaf_index, error=?err, "MessageProcessor failed processing, enqueue for retry");
Ok(MessageProcessingStatus::Error)
}
Ok(proof) => match self.inbox.process(&message.message, &proof).await {
Ok(outcome) => {
info!(
leaf_index = message_leaf_index,
hash = ?outcome.txid,
"[MessageProcessor] processed"
);
self.db.mark_leaf_as_processed(message_leaf_index)?;
Ok(MessageProcessingStatus::Processed)
}
}
Err(err) => {
error!(leaf_index = message_leaf_index, error=?err, "MessageProcessor failed processing, enqueue for retry");
Ok(MessageProcessingStatus::Error)
}
},
Err(err) => {
error!(error=?err, "MessageProcessor was unable to fetch proof");
bail!("MessageProcessor was unable to fetch proof");
}
}
}
MessageStatus::Proven => match self.inbox.process(&message.message).await {
Ok(outcome) => {
info!(
leaf_index = message_leaf_index,
hash = ?outcome.txid,
"[MessageProcessor] processed a message that was already proven"
);
self.db.mark_leaf_as_processed(message_leaf_index)?;
Ok(MessageProcessingStatus::Processed)
}
Err(err) => {
error!(leaf_index = message_leaf_index, error=?err, "MessageProcessor failed processing, enqueue for retry");
Ok(MessageProcessingStatus::Error)
}
},
MessageStatus::Processed => {
debug!(
leaf_index = message_leaf_index,

@ -234,19 +234,6 @@
"internalType": "bytes",
"name": "_message",
"type": "bytes"
}
],
"name": "process",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "_leaf",
"type": "bytes32"
},
{
"internalType": "bytes32[32]",
@ -257,38 +244,14 @@
"internalType": "uint256",
"name": "_index",
"type": "uint256"
}
],
"name": "prove",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
},
{
"internalType": "bytes",
"name": "_message",
"name": "",
"type": "bytes"
},
{
"internalType": "bytes32[32]",
"name": "_proof",
"type": "bytes32[32]"
},
{
"internalType": "uint256",
"name": "_index",
"type": "uint256"
}
],
"name": "proveAndProcess",
"name": "process",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"

@ -234,30 +234,7 @@ where
}
#[tracing::instrument(err)]
async fn prove(&self, proof: &Proof) -> Result<TxOutcome, ChainCommunicationError> {
let mut sol_proof: [[u8; 32]; 32] = Default::default();
sol_proof
.iter_mut()
.enumerate()
.for_each(|(i, elem)| *elem = proof.path[i].to_fixed_bytes());
let tx = self
.contract
.prove(proof.leaf.into(), sol_proof, proof.index.into());
Ok(report_tx!(tx).into())
}
#[tracing::instrument(err)]
async fn process(&self, message: &AbacusMessage) -> Result<TxOutcome, ChainCommunicationError> {
let tx = self.contract.process(message.to_vec().into());
let gas = tx.estimate_gas().await?.saturating_add(U256::from(100000));
let gassed = tx.gas(gas);
Ok(report_tx!(gassed).into())
}
#[tracing::instrument(err)]
async fn prove_and_process(
async fn process(
&self,
message: &AbacusMessage,
proof: &Proof,
@ -268,10 +245,13 @@ where
.enumerate()
.for_each(|(i, elem)| *elem = proof.path[i].to_fixed_bytes());
//
let tx =
self.contract
.prove_and_process(message.to_vec().into(), sol_proof, proof.index.into());
let tx = self.contract.process(
message.to_vec().into(),
sol_proof,
proof.index.into(),
// Sovereign Consensus is not yet implemented
Default::default(),
);
let gas = tx.estimate_gas().await?.saturating_add(U256::from(100000));
let gassed = tx.gas(gas);
Ok(report_tx!(gassed).into())
@ -282,8 +262,7 @@ where
let status = self.contract.messages(leaf.into()).call().await?;
match status {
0 => Ok(MessageStatus::None),
1 => Ok(MessageStatus::Proven),
2 => Ok(MessageStatus::Processed),
1 => Ok(MessageStatus::Processed),
_ => panic!("Bad status from solidity"),
}
}

@ -23,7 +23,7 @@
"url": ""
},
"addresses": {
"inbox": "0x84eA74d481Ee0A5332c457a4d796187F6Ba67fEB",
"inbox": "0x851356ae760d987E095750cCeb3bC6014560891C",
"validatorManager": "0x67d269191c92Caf3cD7723F116c85e6E9bf55933"
}
},
@ -36,7 +36,7 @@
"url": ""
},
"addresses": {
"inbox": "0xb7278A61aa25c888815aFC32Ad3cC52fF24fE575",
"inbox": "0x7bc06c482DEAd17c0e297aFbC32f6e63d3846650",
"validatorManager": "0x4c5859f0F772848b2D91F1D83E2Fe57935348029"
}
},
@ -49,7 +49,7 @@
"url": ""
},
"addresses": {
"inbox": "0xD8a5a9b31c3C0232E196d518E89Fd8bF83AcAd43",
"inbox": "0x202CCe504e04bEd6fC0521238dDf04Bc9E8E15aB",
"validatorManager": "0x4C4a2f8c81640e47606d3fd77B353E87Ba015584"
}
}
@ -59,4 +59,4 @@
"fmt": "json"
},
"db": "db_path"
}
}

@ -3,7 +3,7 @@
"signers": {},
"inboxes": {
"alfajores": {
"address": "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE",
"address": "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c",
"domain": "1000",
"name": "alfajores",
"rpcStyle": "ethereum",
@ -13,7 +13,7 @@
}
},
"kovan": {
"address": "0x851356ae760d987E095750cCeb3bC6014560891C",
"address": "0x70e0bA845a1A0F2DA3359C97E0285013525FFC49",
"domain": "3000",
"name": "kovan",
"rpcStyle": "ethereum",
@ -23,7 +23,7 @@
}
},
"mumbai": {
"address": "0x2bdCC0de6bE1f7D2ee689a0342D76F52E8EFABa3",
"address": "0x1429859428C0aBc9C2C47C8Ee9FBaf82cFA0F20f",
"domain": "80001",
"name": "mumbai",
"rpcStyle": "ethereum",
@ -34,7 +34,7 @@
}
},
"outbox": {
"address": "0x5081a39b8A5f0E35a8D959395a630b68B74Dd30f",
"address": "0x2E2Ed0Cfd3AD2f1d34481277b3204d807Ca2F8c2",
"domain": "43113",
"name": "fuji",
"rpcStyle": "ethereum",

@ -3,7 +3,7 @@
"signers": {},
"inboxes": {
"alfajores": {
"address": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82",
"address": "0x0B306BF915C4d645ff596e518fAf3F9669b97016",
"domain": "1000",
"name": "alfajores",
"rpcStyle": "ethereum",
@ -13,7 +13,7 @@
}
},
"mumbai": {
"address": "0xCD8a1C3ba11CF5ECfa6267617243239504a98d90",
"address": "0xFD471836031dc5108809D173A067e8486B9047A3",
"domain": "80001",
"name": "mumbai",
"rpcStyle": "ethereum",
@ -23,7 +23,7 @@
}
},
"fuji": {
"address": "0xD8a5a9b31c3C0232E196d518E89Fd8bF83AcAd43",
"address": "0x172076E0166D1F9Cc711C77Adf8488051744980C",
"domain": "43113",
"name": "fuji",
"rpcStyle": "ethereum",
@ -34,7 +34,7 @@
}
},
"outbox": {
"address": "0x09635F643e140090A9A8Dcd712eD6285858ceBef",
"address": "0x67d269191c92Caf3cD7723F116c85e6E9bf55933",
"domain": "3000",
"name": "kovan",
"rpcStyle": "ethereum",

@ -3,7 +3,7 @@
"signers": {},
"inboxes": {
"alfajores": {
"address": "0x0B306BF915C4d645ff596e518fAf3F9669b97016",
"address": "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE",
"domain": "1000",
"name": "alfajores",
"rpcStyle": "ethereum",
@ -13,7 +13,7 @@
}
},
"kovan": {
"address": "0xa82fF9aFd8f496c3d6ac40E2a0F282E47488CFc9",
"address": "0x95401dc811bb5740090279Ba06cfA8fcF6113778",
"domain": "3000",
"name": "kovan",
"rpcStyle": "ethereum",
@ -23,7 +23,7 @@
}
},
"fuji": {
"address": "0x51A1ceB83B83F1985a81C295d1fF28Afef186E02",
"address": "0xBEc49fA140aCaA83533fB00A2BB19bDdd0290f25",
"domain": "43113",
"name": "fuji",
"rpcStyle": "ethereum",
@ -34,7 +34,7 @@
}
},
"outbox": {
"address": "0x5eb3Bc0a489C5A8288765d2336659EbCA68FCd00",
"address": "0x1291Be112d480055DaFd8a610b7d1e203891C274",
"domain": "80001",
"name": "mumbai",
"rpcStyle": "ethereum",

@ -73,8 +73,7 @@ impl ProveCommand {
let status = inbox.message_status(message.to_leaf()).await?;
let outcome = match status {
MessageStatus::None => inbox.prove_and_process(&message, &proof).await?,
MessageStatus::Proven => inbox.process(&message).await?,
MessageStatus::None => inbox.process(&message, &proof).await?,
_ => {
println!("Message already processed.");
return Ok(());

@ -28,12 +28,10 @@ contract Inbox is IInbox, Version0, Common {
// ============ Enums ============
// Status of Message:
// 0 - None - message has not been proven or processed
// 1 - Proven - message inclusion proof has been validated
// 2 - Processed - message has been dispatched to recipient
// 0 - None - message has not been processed
// 1 - Processed - message has been dispatched to recipient
enum MessageStatus {
None,
Proven,
Processed
}
@ -99,79 +97,62 @@ contract Inbox is IInbox, Version0, Common {
}
/**
* @notice First attempts to prove the validity of provided formatted
* `message`. If the message is successfully proven, then tries to process
* message.
* @dev Reverts if `prove` call returns false
* @notice Attempts to process the provided formatted `message`. Performs
* verification against root of the proof
* @dev Reverts if verification of the message fails.
* @dev Includes the eventual function signature for Sovereign Consensus,
* but comments out the name to suppress compiler warning
* @param _message Formatted message (refer to Common.sol Message library)
* @param _proof Merkle proof of inclusion for message's leaf
* @param _index Index of leaf in outbox's merkle tree
*/
function proveAndProcess(
function process(
bytes calldata _message,
bytes32[32] calldata _proof,
uint256 _index
uint256 _index,
bytes calldata /* _sovereignData */
) external override {
require(prove(keccak256(_message), _proof, _index), "!prove");
process(_message);
// check re-entrancy guard
require(entered == 1, "!reentrant");
entered = 0;
bytes32 _messageHash = keccak256(_message);
// ensure that message has not been processed
require(
messages[_messageHash] == MessageStatus.None,
"!MessageStatus.None"
);
// calculate the expected root based on the proof
bytes32 _calculatedRoot = MerkleLib.branchRoot(
_messageHash,
_proof,
_index
);
// ensure that the root has been checkpointed
require(checkpoints[_calculatedRoot] > 0, "!checkpointed root");
_process(_message, _messageHash);
// reset re-entrancy guard
entered = 1;
}
// ============ Public Functions ============
// ============ Internal Functions ============
/**
* @notice Given formatted message, attempts to dispatch
* message payload to end recipient.
* @dev Recipient must implement a `handle` method (refer to IMessageRecipient.sol)
* Reverts if formatted message's destination domain is not the Inbox's domain,
* if message has not been proven, or if the dispatch transaction fails.
* @param _message Formatted message
* @notice Marks a message as processed and calls handle on the recipient
* @dev Internal function that can be called by contracts like TestInbox
* @param _message Formatted message (refer to Common.sol Message library)
* @param _messageHash keccak256 hash of the message
*/
function process(bytes calldata _message) public {
function _process(bytes calldata _message, bytes32 _messageHash) internal {
bytes29 _m = _message.ref(0);
// ensure message was meant for this domain
require(_m.destination() == localDomain, "!destination");
// ensure message has been proven
bytes32 _messageHash = _m.keccak();
require(messages[_messageHash] == MessageStatus.Proven, "!proven");
// check re-entrancy guard
require(entered == 1, "!reentrant");
entered = 0;
// update message status as processed
messages[_messageHash] = MessageStatus.Processed;
IMessageRecipient _recipient = IMessageRecipient(_m.recipientAddress());
_recipient.handle(_m.origin(), _m.sender(), _m.body().clone());
// emit process results
emit Process(_messageHash);
// reset re-entrancy guard
entered = 1;
}
/**
* @notice Attempts to prove the validity of message given its leaf, the
* merkle proof of inclusion for the leaf, and the index of the leaf.
* @dev Reverts if message's MessageStatus != None (i.e. if message was
* already proven or processed)
* @dev For convenience, we allow proving against any previous root.
* This means that witnesses never need to be updated for the new root
* @param _leaf Leaf of message to prove
* @param _proof Merkle proof of inclusion for leaf
* @param _index Index of leaf in outbox's merkle tree
* @return Returns true if proof was valid and `prove` call succeeded
**/
function prove(
bytes32 _leaf,
bytes32[32] calldata _proof,
uint256 _index
) public returns (bool) {
// ensure that message has not been proven or processed
require(messages[_leaf] == MessageStatus.None, "!MessageStatus.None");
// calculate the expected root based on the proof
bytes32 _calculatedRoot = MerkleLib.branchRoot(_leaf, _proof, _index);
// if the root is valid, change status to Proven
if (checkpoints[_calculatedRoot] > 0) {
messages[_leaf] = MessageStatus.Proven;
return true;
}
return false;
}
}

@ -10,11 +10,6 @@ contract TestInbox is Inbox {
constructor(uint32 _localDomain) Inbox(_localDomain) {} // solhint-disable-line no-empty-blocks
function setMessageProven(bytes memory _message) external {
bytes29 _m = _message.ref(0);
messages[_m.keccak()] = MessageStatus.Proven;
}
function setCheckpoint(bytes32 _root, uint256 _index) external {
checkpoints[_root] = _index;
}
@ -28,7 +23,12 @@ contract TestInbox is Inbox {
}
function testProcess(bytes calldata _message) external {
process(_message);
bytes32 _messageHash = keccak256(_message);
_process(_message, _messageHash);
}
function setMessageStatus(bytes32 _leaf, MessageStatus status) external {
messages[_leaf] = status;
}
function getRevertMsg(bytes memory _res)

@ -10,10 +10,11 @@ interface IInbox is ICommon {
) external;
function remoteDomain() external returns (uint32);
function proveAndProcess(
function process(
bytes calldata _message,
bytes32[32] calldata _proof,
uint256 _index
uint256 _index,
bytes calldata _sovereignData
) external;
}

@ -14,9 +14,10 @@ import {
TestValidatorManager,
TestValidatorManager__factory,
} from '../types';
import { MessageStatus } from '@abacus-network/utils/dist/src/types';
const merkleTestCases = require('../../../vectors/merkle.json');
const proveAndProcessTestCases = require('../../../vectors/proveAndProcess.json');
const messageWithProof = require('../../../vectors/messageWithProof.json');
const localDomain = 2000;
const remoteDomain = 1000;
@ -99,101 +100,47 @@ describe('Inbox', async () => {
).to.be.revertedWith('old checkpoint');
});
it('Proves a valid message', async () => {
// Use 1st proof of 1st merkle vector test case
const testCase = merkleTestCases[0];
let { leaf, index, path } = testCase.proofs[0];
it('Processes a valid message', async () => {
let { index, proof, root, message } = messageWithProof;
await inbox.setCheckpoint(root, 1);
await inbox.setCheckpoint(testCase.expectedRoot, 1);
// Ensure proper static call return value
expect(await inbox.callStatic.prove(leaf, path as types.BytesArray, index))
.to.be.true;
await inbox.prove(leaf, path as types.BytesArray, index);
expect(await inbox.messages(leaf)).to.equal(types.MessageStatus.PENDING);
// We assume correctness of processing via a successful call to a non-existing account
await expect(inbox.process(message, proof, index, '0x')).to.be.revertedWith(
'function call to a non-contract account',
);
});
it('Rejects an already-proven message', async () => {
const testCase = merkleTestCases[0];
let { leaf, index, path } = testCase.proofs[0];
it('Rejects an already-processed message', async () => {
let { leaf, index, proof, root, message } = messageWithProof;
await inbox.setCheckpoint(testCase.expectedRoot, 1);
await inbox.setCheckpoint(root, 1);
// Set message status as MessageStatus.Processed
await inbox.setMessageStatus(leaf, MessageStatus.PROCESSED);
// Prove message, which changes status to MessageStatus.Pending
await inbox.prove(leaf, path as types.BytesArray, index);
expect(await inbox.messages(leaf)).to.equal(types.MessageStatus.PENDING);
// Try to prove message again
await expect(
inbox.prove(leaf, path as types.BytesArray, index),
).to.be.revertedWith('!MessageStatus.None');
// Try to process message again
await expect(inbox.process(message, proof, index, '0x')).to.be.revertedWith(
'!MessageStatus.None',
);
});
it('Rejects invalid message proof', async () => {
// Use 1st proof of 1st merkle vector test case
const testCase = merkleTestCases[0];
let { leaf, index, path } = testCase.proofs[0];
let { leaf, index, proof, root, message } = messageWithProof;
// Switch ordering of proof hashes
// NB: We copy 'path' here to avoid mutating the test cases for
// other tests.
const newPath = [...path];
newPath[0] = path[1];
newPath[1] = path[0];
const newProof = [...proof];
newProof[0] = proof[1];
newProof[1] = proof[0];
await inbox.setCheckpoint(testCase.expectedRoot, 1);
await inbox.setCheckpoint(root, 1);
expect(
await inbox.callStatic.prove(leaf, newPath as types.BytesArray, index),
).to.be.false;
await inbox.prove(leaf, newPath as types.BytesArray, index);
inbox.process(message, newProof as types.BytesArray, index, '0x'),
).to.be.revertedWith('!checkpointed root');
expect(await inbox.messages(leaf)).to.equal(types.MessageStatus.NONE);
});
it('Processes a proved message', async () => {
const sender = abacusMessageSender;
const testRecipientFactory = new TestRecipient__factory(signer);
const testRecipient = await testRecipientFactory.deploy();
const nonce = 0;
const abacusMessage = utils.formatMessage(
remoteDomain,
sender.address,
nonce,
localDomain,
testRecipient.address,
'0x',
);
// Set message status to types.MessageStatus.Pending
await inbox.setMessageProven(abacusMessage);
const processTx = inbox.process(abacusMessage);
await expect(processTx)
.to.emit(inbox, 'Process')
.withArgs(utils.messageHash(abacusMessage));
});
it('Fails to process an unproved message', async () => {
const [sender, recipient] = await ethers.getSigners();
const nonce = 0;
const body = ethers.utils.formatBytes32String('message');
const abacusMessage = utils.formatMessage(
remoteDomain,
sender.address,
nonce,
localDomain,
recipient.address,
body,
);
await expect(inbox.process(abacusMessage)).to.be.revertedWith('!proven');
});
for (let i = 0; i < badRecipientFactories.length; i++) {
it(`Fails to process a message for a badly implemented recipient (${
i + 1
@ -212,9 +159,7 @@ describe('Inbox', async () => {
'0x',
);
// Set message status to MessageStatus.Pending
await inbox.setMessageProven(abacusMessage);
await expect(inbox.process(abacusMessage)).to.be.reverted;
await expect(inbox.testProcess(abacusMessage)).to.be.reverted;
});
}
@ -233,7 +178,7 @@ describe('Inbox', async () => {
body,
);
await expect(inbox.process(abacusMessage)).to.be.revertedWith(
await expect(inbox.testProcess(abacusMessage)).to.be.revertedWith(
'!destination',
);
});
@ -251,9 +196,7 @@ describe('Inbox', async () => {
body,
);
// Set message status to types.MessageStatus.Pending
await inbox.setMessageProven(abacusMessage);
await expect(inbox.process(abacusMessage)).to.be.reverted;
await expect(inbox.testProcess(abacusMessage)).to.be.reverted;
});
it('Fails to process a message for bad handler function', async () => {
@ -272,11 +215,30 @@ describe('Inbox', async () => {
'0x',
);
// Set message status to MessageStatus.Pending
await inbox.setMessageProven(abacusMessage);
// Ensure bad handler function causes process to fail
await expect(inbox.process(abacusMessage)).to.be.reverted;
await expect(inbox.testProcess(abacusMessage)).to.be.reverted;
});
it('Processes a message directly', async () => {
const sender = abacusMessageSender;
const [recipient] = await ethers.getSigners();
const factory = new TestRecipient__factory(recipient);
const testRecipient = await factory.deploy();
const nonce = 0;
const abacusMessage = utils.formatMessage(
remoteDomain,
sender.address,
nonce,
localDomain,
testRecipient.address,
'0x',
);
await inbox.testProcess(abacusMessage);
const hash = utils.messageHash(abacusMessage);
expect(await inbox.messages(hash)).to.eql(MessageStatus.PROCESSED);
});
it('Proves and processes a message', async () => {
@ -313,41 +275,8 @@ describe('Inbox', async () => {
);
await inbox.setCheckpoint(proofRoot, 1);
await inbox.proveAndProcess(abacusMessage, path as types.BytesArray, index);
await inbox.process(abacusMessage, path as types.BytesArray, index, '0x');
expect(await inbox.messages(hash)).to.equal(types.MessageStatus.PROCESSED);
});
it('Has proveAndProcess fail if prove fails', async () => {
const [sender, recipient] = await ethers.getSigners();
const nonce = 0;
// Use 1st proof of 1st merkle vector test case
const testCase = merkleTestCases[0];
let { leaf, index, path } = testCase.proofs[0];
// Create arbitrary message (contents not important)
const abacusMessage = utils.formatMessage(
remoteDomain,
sender.address,
nonce,
localDomain,
recipient.address,
'0x',
);
// Ensure root given in proof and actual root don't match so that
// inbox.prove(...) will fail
const proofRoot = await inbox.testBranchRoot(
leaf,
path as types.BytesArray,
index,
);
const rootIndex = await inbox.checkpoints(proofRoot);
expect(rootIndex).to.equal(0);
await expect(
inbox.proveAndProcess(abacusMessage, path as types.BytesArray, index),
).to.be.revertedWith('!prove');
});
});

@ -16,7 +16,7 @@
"test": "test"
},
"dependencies": {
"@abacus-network/core": "^0.0.5",
"@abacus-network/core": "^0.0.6",
"@abacus-network/utils": "^0.0.7",
"@nomiclabs/hardhat-ethers": "^2.0.5",
"@nomiclabs/hardhat-waffle": "^2.0.2",

@ -182,7 +182,6 @@ export class TestAbacusDeploy extends TestDeploy<
const inbox = this.inbox(origin, destination);
const status = await inbox.messages(dispatch.args.messageHash);
if (status !== types.MessageStatus.PROCESSED) {
await inbox.setMessageProven(dispatch.args.message);
const response = await inbox.testProcess(dispatch.args.message);
let destinationResponses = responses.get(destination) || [];
destinationResponses.push(response);

@ -60,6 +60,5 @@ export enum AbacusState {
export enum MessageStatus {
NONE = 0,
PENDING,
PROCESSED,
}

@ -0,0 +1,40 @@
{
"leaf": "0x07e012e0b6de0a26db2267eecb0c677ac48e581d8141c2e76bbfa453bba2871e",
"index": 0,
"proof": [
"0xd617f1d4e0e7162d7d484ad95a2e4ba4fc4b532b97b04dd0545d16d8ad99dd79",
"0x5ab30f4218ff80164db21b0f917c2b219cf46c781b4b09739caf1238a8ab9f91",
"0x5be03a46bfae6872cd3833b65a5fde75cad34ea5b33f84d5ab452cdeeaac0020",
"0x21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85",
"0xe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344",
"0x0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d",
"0x887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968",
"0xffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83",
"0x9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af",
"0xcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0",
"0xf9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5",
"0xf8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892",
"0x3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c",
"0xc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb",
"0x5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc",
"0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2",
"0x2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f",
"0xe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a",
"0x5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0",
"0xb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0",
"0xc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2",
"0xf4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9",
"0x5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377",
"0x4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652",
"0xcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef",
"0x0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d",
"0xb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0",
"0x838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e",
"0x662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e",
"0x388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322",
"0x93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735",
"0x8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9"
],
"message": "0x000003e8000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000007d00000000000000000000000002b0d36facd61b71cc05ab8f3d2355ec3631c0dd51234",
"root": "0x4832efe713a444c06636cd38325c9be3ab06061db0489ecf18a97ec25d12bd22"
}
Loading…
Cancel
Save