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

Dev #3

Merged
merged 5 commits into from
May 20, 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
11 changes: 11 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[*]
charset = utf-8
end_of_line = lf
indent_style = space
insert_final_newline = true
[*.ts]
indent_size = 2
max_line_length = 120
[*.sol]
indent_size = 4
max_line_length = 99
12 changes: 12 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Deployer private key
PRIVATE_KEY=YOUR PRIVATE KEY

# RPC Endpoints
INFURA_KEY=INFURA PROJECT ID
ALCHEMY_KEY=ALCHEMY PROJECT ID

# Additional keys
ETHERSCAN_KEY=ETHERSCAN API KEY
BSCSCAN_KEY=BSCSCAN API KEY
POLYGONSCAN_KEY=POLYGONSCAN API KEY
COINMARKETCAP_KEY=COINMARKETCAP API KEY
22 changes: 22 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module",
"project": "./tsconfig.json"
},
"plugins": ["@typescript-eslint"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended"
],
"rules": {
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-floating-promises": "error"
},
"env": {
"browser": true,
"es2021": true
}
}
15 changes: 15 additions & 0 deletions .github/actions/setup/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: setup

description: setup

runs:
using: composite
steps:
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: "18.16.x"
cache: npm
- name: Install packages
run: npm install
shell: bash
23 changes: 23 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: "checks"

on:
push:
branches:
- master
pull_request:
branches:
- master
- dev

jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup
- name: Run lint
run: npm run lint
- name: Run tests
run: npm run test
19 changes: 19 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
node_modules
.env
.DS_Store

# Hardhat files
cache
artifacts
coverage.json
coverage

# Typechain generated files
generated-types

# Migration files
.storage.json

# IDE files
.idea
.vscode
1 change: 1 addition & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
npm run lint-fix && git add -u
13 changes: 13 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
node_modules
.env
.DS_Store
package-lock.json

# Hardhat files
cache
artifacts
coverage.json
coverage

# Typechain generated files
generated-types
27 changes: 27 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"overrides": [
{
"files": "*.sol",
"options": {
"plugins": ["prettier-plugin-solidity"],
"printWidth": 120,
"tabWidth": 4,
"useTabs": false,
"singleQuote": false,
"bracketSpacing": false
}
},
{
"files": "*.ts",
"options": {
"plugins": ["@trivago/prettier-plugin-sort-imports"],
"printWidth": 120,
"tabWidth": 2,
"singleQuote": true,
"importOrder": ["^[./]", "^@/(.*)$"],
"importOrderSeparation": true,
"importOrderSortSpecifiers": true
}
}
]
}
4 changes: 4 additions & 0 deletions .solcover.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
skipFiles: ['mock', 'interfaces', '@layerzerolabs'],
configureYulOptimizer: true,
};
15 changes: 15 additions & 0 deletions .solhint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"extends": "solhint:recommended",
"plugins": ["prettier"],
"rules": {
"reentrancy": "off",
"prettier/prettier": "error",
"modifier-name-mixedcase": "off",
"no-empty-blocks": "off",
"func-visibility": "off",
"max-states-count": "off",
"not-rely-on-time": "off",
"compiler-version": "off",
"no-inline-assembly": "off"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// SPDX-License-Identifier: LZBL-1.2

pragma solidity ^0.8.20;

import {BytesLib} from "solidity-bytes-utils/contracts/BytesLib.sol";

import {BitMap256} from "@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/BitMaps.sol";
import {CalldataBytesLib} from "@layerzerolabs/lz-evm-protocol-v2/contracts/libs/CalldataBytesLib.sol";

library DVNOptions {
using CalldataBytesLib for bytes;
using BytesLib for bytes;

uint8 internal constant WORKER_ID = 2;
uint8 internal constant OPTION_TYPE_PRECRIME = 1;

error DVN_InvalidDVNIdx();
error DVN_InvalidDVNOptions(uint256 cursor);

/// @dev group dvn options by its idx
/// @param _options [dvn_id][dvn_option][dvn_id][dvn_option]...
/// dvn_option = [option_size][dvn_idx][option_type][option]
/// option_size = len(dvn_idx) + len(option_type) + len(option)
/// dvn_id: uint8, dvn_idx: uint8, option_size: uint16, option_type: uint8, option: bytes
/// @return dvnOptions the grouped options, still share the same format of _options
/// @return dvnIndices the dvn indices
function groupDVNOptionsByIdx(
bytes memory _options
) internal pure returns (bytes[] memory dvnOptions, uint8[] memory dvnIndices) {
if (_options.length == 0) return (dvnOptions, dvnIndices);

uint8 numDVNs = getNumDVNs(_options);

// if there is only 1 dvn, we can just return the whole options
if (numDVNs == 1) {
dvnOptions = new bytes[](1);
dvnOptions[0] = _options;

dvnIndices = new uint8[](1);
dvnIndices[0] = _options.toUint8(3); // dvn idx
return (dvnOptions, dvnIndices);
}

// otherwise, we need to group the options by dvn_idx
dvnIndices = new uint8[](numDVNs);
dvnOptions = new bytes[](numDVNs);
unchecked {
uint256 cursor = 0;
uint256 start = 0;
uint8 lastDVNIdx = 255; // 255 is an invalid dvn_idx

while (cursor < _options.length) {
++cursor; // skip worker_id

// optionLength asserted in getNumDVNs (skip check)
uint16 optionLength = _options.toUint16(cursor);
cursor += 2;

// dvnIdx asserted in getNumDVNs (skip check)
uint8 dvnIdx = _options.toUint8(cursor);

// dvnIdx must equal to the lastDVNIdx for the first option
// so it is always skipped in the first option
// this operation slices out options whenever the scan finds a different lastDVNIdx
if (lastDVNIdx == 255) {
lastDVNIdx = dvnIdx;
} else if (dvnIdx != lastDVNIdx) {
uint256 len = cursor - start - 3; // 3 is for worker_id and option_length
bytes memory opt = _options.slice(start, len);
_insertDVNOptions(dvnOptions, dvnIndices, lastDVNIdx, opt);

// reset the start and lastDVNIdx
start += len;
lastDVNIdx = dvnIdx;
}

cursor += optionLength;
}

// skip check the cursor here because the cursor is asserted in getNumDVNs
// if we have reached the end of the options, we need to process the last dvn
uint256 size = cursor - start;
bytes memory op = _options.slice(start, size);
_insertDVNOptions(dvnOptions, dvnIndices, lastDVNIdx, op);

// revert dvnIndices to start from 0
for (uint8 i = 0; i < numDVNs; ++i) {
--dvnIndices[i];
}
}
}

function _insertDVNOptions(
bytes[] memory _dvnOptions,
uint8[] memory _dvnIndices,
uint8 _dvnIdx,
bytes memory _newOptions
) internal pure {
// dvnIdx starts from 0 but default value of dvnIndices is 0,
// so we tell if the slot is empty by adding 1 to dvnIdx
if (_dvnIdx == 255) revert DVN_InvalidDVNIdx();
uint8 dvnIdxAdj = _dvnIdx + 1;

for (uint256 j = 0; j < _dvnIndices.length; ++j) {
uint8 index = _dvnIndices[j];
if (dvnIdxAdj == index) {
_dvnOptions[j] = abi.encodePacked(_dvnOptions[j], _newOptions);
break;
} else if (index == 0) {
// empty slot, that means it is the first time we see this dvn
_dvnIndices[j] = dvnIdxAdj;
_dvnOptions[j] = _newOptions;
break;
}
}
}

/// @dev get the number of unique dvns
/// @param _options the format is the same as groupDVNOptionsByIdx
function getNumDVNs(bytes memory _options) internal pure returns (uint8 numDVNs) {
uint256 cursor = 0;
BitMap256 bitmap;

// find number of unique dvn_idx
unchecked {
while (cursor < _options.length) {
++cursor; // skip worker_id

uint16 optionLength = _options.toUint16(cursor);
cursor += 2;
if (optionLength < 2) revert DVN_InvalidDVNOptions(cursor); // at least 1 byte for dvn_idx and 1 byte for option_type

uint8 dvnIdx = _options.toUint8(cursor);

// if dvnIdx is not set, increment numDVNs
// max num of dvns is 255, 255 is an invalid dvn_idx
// The order of the dvnIdx is not required to be sequential, as enforcing the order may weaken
// the composability of the options. e.g. if we refrain from enforcing the order, an OApp that has
// already enforced certain options can append additional options to the end of the enforced
// ones without restrictions.
if (dvnIdx == 255) revert DVN_InvalidDVNIdx();
if (!bitmap.get(dvnIdx)) {
++numDVNs;
bitmap = bitmap.set(dvnIdx);
}

cursor += optionLength;
}
}
if (cursor != _options.length) revert DVN_InvalidDVNOptions(cursor);
}

/// @dev decode the next dvn option from _options starting from the specified cursor
/// @param _options the format is the same as groupDVNOptionsByIdx
/// @param _cursor the cursor to start decoding
/// @return optionType the type of the option
/// @return option the option
/// @return cursor the cursor to start decoding the next option
function nextDVNOption(
bytes calldata _options,
uint256 _cursor
) internal pure returns (uint8 optionType, bytes calldata option, uint256 cursor) {
unchecked {
// skip worker id
cursor = _cursor + 1;

// read option size
uint16 size = _options.toU16(cursor);
cursor += 2;

// read option type
optionType = _options.toU8(cursor + 1); // skip dvn_idx

// startCursor and endCursor are used to slice the option from _options
uint256 startCursor = cursor + 2; // skip option type and dvn_idx
uint256 endCursor = cursor + size;
option = _options[startCursor:endCursor];
cursor += size;
}
}
}
39 changes: 39 additions & 0 deletions contracts/@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/OApp.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

// @dev Import the 'MessagingFee' and 'MessagingReceipt' so it's exposed to OApp implementers
// solhint-disable-next-line no-unused-import
import {OAppSender, MessagingFee, MessagingReceipt} from "./OAppSender.sol";
// @dev Import the 'Origin' so it's exposed to OApp implementers
// solhint-disable-next-line no-unused-import
import {OAppReceiver, Origin} from "./OAppReceiver.sol";
import {OAppCore} from "./OAppCore.sol";

/**
* @title OApp
* @dev Abstract contract serving as the base for OApp implementation, combining OAppSender and OAppReceiver functionality.
*/
abstract contract OApp is OAppSender, OAppReceiver {
/**
* @dev Constructor to initialize the OApp with the provided endpoint and owner.
* @param _endpoint The address of the LOCAL LayerZero endpoint.
* @param _delegate The delegate capable of making OApp configurations inside of the endpoint.
*/
constructor(address _endpoint, address _delegate) OAppCore(_endpoint, _delegate) {}

/**
* @notice Retrieves the OApp version information.
* @return senderVersion The version of the OAppSender.sol implementation.
* @return receiverVersion The version of the OAppReceiver.sol implementation.
*/
function oAppVersion()
public
pure
virtual
override(OAppSender, OAppReceiver)
returns (uint64 senderVersion, uint64 receiverVersion)
{
return (SENDER_VERSION, RECEIVER_VERSION);
}
}
Loading