Skip to content

Commit

Permalink
[protocol] add more tests (#179)
Browse files Browse the repository at this point in the history
  • Loading branch information
cyberhorsey committed Oct 22, 2022
1 parent b9c1343 commit 2efcc6c
Show file tree
Hide file tree
Showing 60 changed files with 2,295 additions and 218 deletions.
2 changes: 1 addition & 1 deletion packages/protocol/.prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ artifacts
cache
coverage*
gasReporterOutput.json
contracts/thirdparty/Lib_BlockHeaderDecoder.sol
contracts/thirdparty/LibBlockHeaderDecoder.sol
9 changes: 9 additions & 0 deletions packages/protocol/.solcover.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = {
configureYulOptimizer: true,
skipFiles: [
'thirdparty/LibBlockHeaderDecoder.sol', // assembly too long
'libs/LibReceiptDecoder.sol', //integration test,
'test/libs/TestLibReceiptDecoder.sol', //integration tests
'test/thirdparty/TestLibBlockHeaderDecoder.sol' // assembly too long
]
};
6 changes: 3 additions & 3 deletions packages/protocol/.solhintignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
node_modules/
contracts/aux/tokens/ERC20Upgradeable.sol
contracts/test/TestLib_RLPReader.sol
contracts/test/TestLib_RLPWriter.sol
contracts/test/TestLibRLPReader.sol
contracts/test/TestLibRLPWriter.sol
contracts/libs/Lib1559Math.sol
contracts/thirdparty/
**/contracts/thirdparty/**/*.sol
51 changes: 49 additions & 2 deletions packages/protocol/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ yarn test
To run test cases that rely on a go-ethereum node:

```bash
yarn test:geth
yarn test:integration
```

## Generate L2 genesis JSON's `alloc` field
Expand Down Expand Up @@ -56,7 +56,54 @@ the script above will output two JSON files:
- `./deployments/l2_genesis_alloc.json`: the `alloc` field which will be used in L2 genesis JSON file
- `./deployments/l2_genesis_storage_layout.json`: the storage layout of those pre-deployed contracts


## Github Actions

Each commit will automatically trigger the GitHub Actions to run. If any commit message in your push or the HEAD commit of your PR contains the strings [skip ci], [ci skip], [no ci], [skip actions], or [actions skip] workflows triggered on the push or pull_request events will be skipped.

## Contract Flow

### Bridge

`contracts/bridge/Bridge.sol` is to be deployed on both the Layer 1 and Layer 2 chains.

#### Bridging Ether

To bridge from Layer 1 to Layer 2, the process starts at `Bridge.sendMessage(message)` on Layer 1. The message must have a `depositValue`, `callValue`, and `processingFee` that sum up to the amount of Ether `msg.value`. The `destChainId` of the message must be different than `block.chainid`, and the chainId must be enabled in `state.destChains` by calling `Bridge.enableDestChain(chainId)`.

The `msg.value` amount of Ether will be sent to the Layer1 `EtherVault` contract to be held. Then, the mesage will be hashed and stored as a `signal`.

The bridge will use `LibBridgeSignal.sendSignal(bridgeContractAddress, signalHash)` to store the signal being sent as a boolean `1`, and then use `LibBridgeData.MessageSent(signal, message)` event as an indicator to an off-chain relayer that a message has been sent to be processed on Layer 2.

On the Layer 2 Bridge, the relayer, upon receipt of the Layer 1 `LibBridgeData.MessageSent` event, `Bridge.processMessage(message, proof)` must be called. A proof must be generated to pass in as the `proof`. `processMessage` will use `LibBridgeProcess.processMessage(state, addressResolver, message, proof)` to verify the message is valid.

`LibBridgeProces.processMessage` requires the message's `destChainId`, set earlier, is equal to the current `block.chainid`. Then, it will hash the message to generate the `signal`, and then require the internal `state.messageStatus` of the signal is equal to `LibBridgeData.MessageStatus.NEW` to ensure the message has not been processed already.

It will then require that the signal has been received, using `LibBridgeSignal.isSignalReceived(resolver, srcBridge, sender, signal, proof)`, and, using Merkle Trie verification via `LibTrieProof`, and the synced header hash from the `TaikoL2` contract, make sure the block has been synced to by comparing the merkle proof block header hash. The proof generated is proof that the storage of the `srcBridge` address contains a key, generated wth `_key(sender, signal)` on LibBridgeSignal, is equal to the value of `bytes32(uint256(1))`, proving that bridge has sent the signal from the Layer 1 chain. You can generate the proof with `eth_getProof` RPC call to Layer 1. The `LibTrieProof` expects the proof to be RLP encoded and concatenated together. In Javascript this looks like:

```ts
const proof = await ethers.provider.send("eth_getProof", [
bridge.address,
[key],
block.hash,
])

// RLP encode the proof together for LibTrieProof to decode
const encodedProof = ethers.utils.defaultAbiCoder.encode(
["bytes", "bytes"],
[RLP.encode(proof.accountProof), RLP.encode(proof.storageProof[0].proof)]
)
```

If that passes verification, the `EtherVault.receiveEither()` call is made to receive Ether on Layer 2. Then, the ether is sent to the `message.owner`.

The message status is updated if it succeeded to `LibBridgeData.MessageStatus.Done`, otherwise, to `LibBridgeData.MessageStatus.RETRIABLE`. Either way, `processMessage` can never be called again for this message. If it failed, caller must use `retryMessage` instead.

To finish off, a refund is calculated and sent to the `message.refundAddress`, or if it is not sent, the `message.owner`. In either case, the `msg.sender` gets the `message.processingFee` for being the first attempt relayer, and the `refundAddress` gets the `refundAmount` if it exists.

The funds should be successfully bridged now.

#### Bridging ERC20

#### Bridging ERC721

#### Bridging ERC1155
20 changes: 10 additions & 10 deletions packages/protocol/contracts/L1/v1/V1Proving.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import "../../libs/LibReceiptDecoder.sol";
import "../../libs/LibTxDecoder.sol";
import "../../libs/LibTxUtils.sol";
import "../../libs/LibZKP.sol";
import "../../thirdparty/Lib_BytesUtils.sol";
import "../../thirdparty/Lib_MerkleTrie.sol";
import "../../thirdparty/Lib_RLPWriter.sol";
import "../../thirdparty/LibBytesUtils.sol";
import "../../thirdparty/LibMerkleTrie.sol";
import "../../thirdparty/LibRLPWriter.sol";
import "../LibData.sol";

/// @author dantaik <dan@taiko.xyz>
Expand Down Expand Up @@ -80,7 +80,7 @@ library V1Proving {

// Check anchor tx's calldata is valid
require(
Lib_BytesUtils.equal(
LibBytesUtils.equal(
_tx.data,
bytes.concat(
LibConstants.V1_ANCHOR_TX_SELECTOR,
Expand All @@ -93,8 +93,8 @@ library V1Proving {

// Check anchor tx is the 1st tx in the block
require(
Lib_MerkleTrie.verifyInclusionProof(
Lib_RLPWriter.writeUint(0),
LibMerkleTrie.verifyInclusionProof(
LibRLPWriter.writeUint(0),
anchorTx,
evidence.proofs[1],
evidence.header.transactionsRoot
Expand All @@ -108,8 +108,8 @@ library V1Proving {

require(receipt.status == 1, "L1:receipt:status");
require(
Lib_MerkleTrie.verifyInclusionProof(
Lib_RLPWriter.writeUint(0),
LibMerkleTrie.verifyInclusionProof(
LibRLPWriter.writeUint(0),
anchorReceipt,
evidence.proofs[2],
evidence.header.receiptsRoot
Expand Down Expand Up @@ -163,8 +163,8 @@ library V1Proving {

// Check the event is the first one in the throw-away block
require(
Lib_MerkleTrie.verifyInclusionProof(
Lib_RLPWriter.writeUint(0),
LibMerkleTrie.verifyInclusionProof(
LibRLPWriter.writeUint(0),
invalidateBlockReceipt,
evidence.proofs[1],
evidence.header.receiptsRoot
Expand Down
2 changes: 1 addition & 1 deletion packages/protocol/contracts/bridge/BridgedERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ contract BridgedERC20 is
string memory _name
) external initializer {
require(
srcToken != address(0) &&
_srcToken != address(0) &&
_srcChainId != 0 &&
_srcChainId != block.chainid &&
bytes(_symbol).length > 0 &&
Expand Down
2 changes: 1 addition & 1 deletion packages/protocol/contracts/libs/LibAnchorSignature.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
// ╱╱┃┃┃╭╮┃┃╭╮┫╰╯┃┃╰━╯┃╭╮┃╰╯┣━━┃
// ╱╱╰╯╰╯╰┻┻╯╰┻━━╯╰━━━┻╯╰┻━━┻━━╯
pragma solidity ^0.8.9;
import "../thirdparty/Lib_Uint512.sol";
import "../thirdparty/LibUint512.sol";

/// @author david <david@taiko.xyz>
library LibAnchorSignature {
Expand Down
34 changes: 17 additions & 17 deletions packages/protocol/contracts/libs/LibBlockHeader.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// ╱╱╰╯╰╯╰┻┻╯╰┻━━╯╰━━━┻╯╰┻━━┻━━╯
pragma solidity ^0.8.9;

import "../thirdparty/Lib_RLPWriter.sol";
import "../thirdparty/LibRLPWriter.sol";
import "./LibConstants.sol";

/// @author david <david@taiko.xyz>
Expand Down Expand Up @@ -40,25 +40,25 @@ library LibBlockHeader {
returns (bytes32)
{
bytes[] memory list = new bytes[](15);
list[0] = Lib_RLPWriter.writeHash(header.parentHash);
list[1] = Lib_RLPWriter.writeHash(header.ommersHash);
list[2] = Lib_RLPWriter.writeAddress(header.beneficiary);
list[3] = Lib_RLPWriter.writeHash(header.stateRoot);
list[4] = Lib_RLPWriter.writeHash(header.transactionsRoot);
list[5] = Lib_RLPWriter.writeHash(header.receiptsRoot);
list[6] = Lib_RLPWriter.writeBytes(abi.encodePacked(header.logsBloom));
list[7] = Lib_RLPWriter.writeUint(header.difficulty);
list[8] = Lib_RLPWriter.writeUint(header.height);
list[9] = Lib_RLPWriter.writeUint64(header.gasLimit);
list[10] = Lib_RLPWriter.writeUint64(header.gasUsed);
list[11] = Lib_RLPWriter.writeUint64(header.timestamp);
list[12] = Lib_RLPWriter.writeBytes(header.extraData);
list[13] = Lib_RLPWriter.writeHash(header.mixHash);
list[0] = LibRLPWriter.writeHash(header.parentHash);
list[1] = LibRLPWriter.writeHash(header.ommersHash);
list[2] = LibRLPWriter.writeAddress(header.beneficiary);
list[3] = LibRLPWriter.writeHash(header.stateRoot);
list[4] = LibRLPWriter.writeHash(header.transactionsRoot);
list[5] = LibRLPWriter.writeHash(header.receiptsRoot);
list[6] = LibRLPWriter.writeBytes(abi.encodePacked(header.logsBloom));
list[7] = LibRLPWriter.writeUint(header.difficulty);
list[8] = LibRLPWriter.writeUint(header.height);
list[9] = LibRLPWriter.writeUint64(header.gasLimit);
list[10] = LibRLPWriter.writeUint64(header.gasUsed);
list[11] = LibRLPWriter.writeUint64(header.timestamp);
list[12] = LibRLPWriter.writeBytes(header.extraData);
list[13] = LibRLPWriter.writeHash(header.mixHash);
// According to the ethereum yellow paper, we should treat `nonce`
// as [8]byte when hashing the block.
list[14] = Lib_RLPWriter.writeBytes(abi.encodePacked(header.nonce));
list[14] = LibRLPWriter.writeBytes(abi.encodePacked(header.nonce));

bytes memory rlpHeader = Lib_RLPWriter.writeList(list);
bytes memory rlpHeader = LibRLPWriter.writeList(list);
return keccak256(rlpHeader);
}

Expand Down
4 changes: 2 additions & 2 deletions packages/protocol/contracts/libs/LibInvalidTxList.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ pragma solidity ^0.8.9;
import "../libs/LibConstants.sol";
import "../libs/LibTxDecoder.sol";
import "../libs/LibTxUtils.sol";
import "../thirdparty/Lib_RLPReader.sol";
import "../thirdparty/Lib_RLPWriter.sol";
import "../thirdparty/LibRLPReader.sol";
import "../thirdparty/LibRLPWriter.sol";

/// @dev A library to invalidate a txList using the following rules:
/// @author david <david@taiko.xyz>
Expand Down
32 changes: 16 additions & 16 deletions packages/protocol/contracts/libs/LibReceiptDecoder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
// ╱╱╰╯╰╯╰┻┻╯╰┻━━╯╰━━━┻╯╰┻━━┻━━╯
pragma solidity ^0.8.9;

import "../thirdparty/Lib_BytesUtils.sol";
import "../thirdparty/Lib_RLPReader.sol";
import "../thirdparty/LibBytesUtils.sol";
import "../thirdparty/LibRLPReader.sol";

/// @author david <david@taiko.xyz>
library LibReceiptDecoder {
Expand All @@ -32,61 +32,61 @@ library LibReceiptDecoder {
returns (Receipt memory receipt)
{
// Non-legacy transaction receipts should remove the type prefix at first.
Lib_RLPReader.RLPItem[] memory rlpItems = Lib_RLPReader.readList(
LibRLPReader.RLPItem[] memory rlpItems = LibRLPReader.readList(
encoded[0] >= 0x0 && encoded[0] <= 0x7f
? Lib_BytesUtils.slice(encoded, 1)
? LibBytesUtils.slice(encoded, 1)
: encoded
);

require(rlpItems.length == 4, "invalid items length");

receipt.status = uint64(Lib_RLPReader.readUint256(rlpItems[0]));
receipt.status = uint64(LibRLPReader.readUint256(rlpItems[0]));
receipt.cumulativeGasUsed = uint64(
Lib_RLPReader.readUint256(rlpItems[1])
LibRLPReader.readUint256(rlpItems[1])
);
receipt.logsBloom = decodeLogsBloom(rlpItems[2]);
receipt.logs = decodeLogs(Lib_RLPReader.readList(rlpItems[3]));
receipt.logs = decodeLogs(LibRLPReader.readList(rlpItems[3]));
}

function decodeLogsBloom(Lib_RLPReader.RLPItem memory logsBloomRlp)
function decodeLogsBloom(LibRLPReader.RLPItem memory logsBloomRlp)
internal
pure
returns (bytes32[8] memory logsBloom)
{
bytes memory bloomBytes = Lib_RLPReader.readBytes(logsBloomRlp);
bytes memory bloomBytes = LibRLPReader.readBytes(logsBloomRlp);
require(bloomBytes.length == 256, "invalid logs bloom");

return abi.decode(bloomBytes, (bytes32[8]));
}

function decodeLogs(Lib_RLPReader.RLPItem[] memory logsRlp)
function decodeLogs(LibRLPReader.RLPItem[] memory logsRlp)
internal
pure
returns (Log[] memory)
{
Log[] memory logs = new Log[](logsRlp.length);

for (uint256 i = 0; i < logsRlp.length; i++) {
Lib_RLPReader.RLPItem[] memory rlpItems = Lib_RLPReader.readList(
LibRLPReader.RLPItem[] memory rlpItems = LibRLPReader.readList(
logsRlp[i]
);
logs[i].contractAddress = Lib_RLPReader.readAddress(rlpItems[0]);
logs[i].topics = decodeTopics(Lib_RLPReader.readList(rlpItems[1]));
logs[i].data = Lib_RLPReader.readBytes(rlpItems[2]);
logs[i].contractAddress = LibRLPReader.readAddress(rlpItems[0]);
logs[i].topics = decodeTopics(LibRLPReader.readList(rlpItems[1]));
logs[i].data = LibRLPReader.readBytes(rlpItems[2]);
}

return logs;
}

function decodeTopics(Lib_RLPReader.RLPItem[] memory topicsRlp)
function decodeTopics(LibRLPReader.RLPItem[] memory topicsRlp)
internal
pure
returns (bytes32[] memory)
{
bytes32[] memory topics = new bytes32[](topicsRlp.length);

for (uint256 i = 0; i < topicsRlp.length; i++) {
topics[i] = Lib_RLPReader.readBytes32(topicsRlp[i]);
topics[i] = LibRLPReader.readBytes32(topicsRlp[i]);
}

return topics;
Expand Down
16 changes: 8 additions & 8 deletions packages/protocol/contracts/libs/LibTrieProof.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
// ╱╱╰╯╰╯╰┻┻╯╰┻━━╯╰━━━┻╯╰┻━━┻━━╯
pragma solidity ^0.8.9;

import "../thirdparty/Lib_RLPReader.sol";
import "../thirdparty/Lib_RLPWriter.sol";
import "../thirdparty/Lib_SecureMerkleTrie.sol";
import "../thirdparty/LibRLPReader.sol";
import "../thirdparty/LibRLPWriter.sol";
import "../thirdparty/LibSecureMerkleTrie.sol";

/// @author dantaik <dan@taiko.xyz>
library LibTrieProof {
Expand Down Expand Up @@ -46,24 +46,24 @@ library LibTrieProof {
(bytes, bytes)
);

(bool exists, bytes memory rlpAccount) = Lib_SecureMerkleTrie.get(
(bool exists, bytes memory rlpAccount) = LibSecureMerkleTrie.get(
abi.encodePacked(addr),
accountProof,
stateRoot
);

require(exists, "LTP:invalid account proof");

Lib_RLPReader.RLPItem[] memory accountState = Lib_RLPReader.readList(
LibRLPReader.RLPItem[] memory accountState = LibRLPReader.readList(
rlpAccount
);
bytes32 storageRoot = Lib_RLPReader.readBytes32(
bytes32 storageRoot = LibRLPReader.readBytes32(
accountState[ACCOUNT_FIELD_INDEX_STORAGE_HASH]
);

bool verified = Lib_SecureMerkleTrie.verifyInclusionProof(
bool verified = LibSecureMerkleTrie.verifyInclusionProof(
abi.encodePacked(key),
Lib_RLPWriter.writeBytes32(value),
LibRLPWriter.writeBytes32(value),
storageProof,
storageRoot
);
Expand Down

1 comment on commit 2efcc6c

@vercel
Copy link

@vercel vercel bot commented on 2efcc6c Oct 22, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.