-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
2,204 additions
and
575 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
name: Test | ||
|
||
on: | ||
pull_request: | ||
branches: [main] | ||
|
||
jobs: | ||
build: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- name: Checkout Repository | ||
uses: actions/checkout@v3 | ||
|
||
- name: Setup Node.js | ||
uses: actions/setup-node@v3 | ||
with: | ||
node-version: "18" | ||
registry-url: "https://registry.npmjs.org" | ||
|
||
- name: Install Dependencies | ||
run: yarn install | ||
|
||
- name: Test | ||
run: yarn test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module.exports = { | ||
preset: "ts-jest", | ||
testEnvironment: "node", | ||
testMatch: ["**/?(*.)+(spec|test).ts?(x)"], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
import { Web3 } from "web3"; | ||
import { ethers } from "ethers"; | ||
import { isAddress } from "web3-validator"; | ||
|
||
// Memo identifier byte | ||
const MemoIdentifier = 0x5a; | ||
|
||
// Enums | ||
enum OpCode { | ||
Deposit = 0b0000, | ||
DepositAndCall = 0b0001, | ||
Call = 0b0010, | ||
Invalid = 0b0011, | ||
} | ||
|
||
enum EncodingFormat { | ||
EncodingFmtABI = 0b0000, | ||
EncodingFmtCompactShort = 0b0001, | ||
EncodingFmtCompactLong = 0b0010, | ||
} | ||
|
||
// Header Class | ||
class Header { | ||
encodingFmt: EncodingFormat; | ||
opCode: OpCode; | ||
|
||
constructor(encodingFmt: EncodingFormat, opCode: OpCode) { | ||
this.encodingFmt = encodingFmt; | ||
this.opCode = opCode; | ||
} | ||
} | ||
|
||
// FieldsV0 Class | ||
class FieldsV0 { | ||
receiver: string; | ||
payload: Uint8Array; | ||
revertAddress: string; | ||
|
||
constructor(receiver: string, payload: Uint8Array, revertAddress: string) { | ||
if (!isAddress(receiver)) { | ||
throw new Error("Invalid receiver address"); | ||
} | ||
this.receiver = receiver; | ||
this.payload = payload; | ||
this.revertAddress = revertAddress; | ||
} | ||
} | ||
|
||
// Main Encoding Function | ||
const encodeToBytes = (header: Header, fields: FieldsV0): Uint8Array => { | ||
if (!header || !fields) { | ||
throw new Error("Header and fields are required"); | ||
} | ||
|
||
// Construct Header Bytes | ||
const headerBytes = new Uint8Array(4); | ||
headerBytes[0] = MemoIdentifier; | ||
headerBytes[1] = (0x00 << 4) | (header.encodingFmt & 0x0f); | ||
headerBytes[2] = ((header.opCode & 0x0f) << 4) | 0x00; | ||
headerBytes[3] = 0b00000111; | ||
|
||
// Encode Fields | ||
let encodedFields: Uint8Array; | ||
switch (header.encodingFmt) { | ||
case EncodingFormat.EncodingFmtABI: | ||
encodedFields = encodeFieldsABI(fields); | ||
break; | ||
case EncodingFormat.EncodingFmtCompactShort: | ||
case EncodingFormat.EncodingFmtCompactLong: | ||
encodedFields = encodeFieldsCompact(header.encodingFmt, fields); | ||
break; | ||
default: | ||
throw new Error("Unsupported encoding format"); | ||
} | ||
|
||
// Combine Header and Fields | ||
return new Uint8Array( | ||
Buffer.concat([Buffer.from(headerBytes), Buffer.from(encodedFields)]) | ||
); | ||
}; | ||
|
||
// Helper: ABI Encoding | ||
const encodeFieldsABI = (fields: FieldsV0): Uint8Array => { | ||
const types = ["address", "bytes", "string"]; | ||
const values = [fields.receiver, fields.payload, fields.revertAddress]; | ||
const encodedData = ethers.utils.defaultAbiCoder.encode(types, values); | ||
return Uint8Array.from(Buffer.from(encodedData.slice(2), "hex")); | ||
}; | ||
|
||
// Helper: Compact Encoding | ||
const encodeFieldsCompact = ( | ||
compactFmt: EncodingFormat, | ||
fields: FieldsV0 | ||
): Uint8Array => { | ||
const encodedReceiver = Buffer.from(Web3.utils.hexToBytes(fields.receiver)); | ||
const encodedPayload = encodeDataCompact(compactFmt, fields.payload); | ||
const encodedRevertAddress = encodeDataCompact( | ||
compactFmt, | ||
new TextEncoder().encode(fields.revertAddress) | ||
); | ||
|
||
return new Uint8Array( | ||
Buffer.concat([encodedReceiver, encodedPayload, encodedRevertAddress]) | ||
); | ||
}; | ||
|
||
// Helper: Compact Data Encoding | ||
const encodeDataCompact = ( | ||
compactFmt: EncodingFormat, | ||
data: Uint8Array | ||
): Uint8Array => { | ||
const dataLen = data.length; | ||
let encodedLength: Buffer; | ||
|
||
switch (compactFmt) { | ||
case EncodingFormat.EncodingFmtCompactShort: | ||
if (dataLen > 255) { | ||
throw new Error( | ||
"Data length exceeds 255 bytes for EncodingFmtCompactShort" | ||
); | ||
} | ||
encodedLength = Buffer.from([dataLen]); | ||
break; | ||
case EncodingFormat.EncodingFmtCompactLong: | ||
if (dataLen > 65535) { | ||
throw new Error( | ||
"Data length exceeds 65535 bytes for EncodingFmtCompactLong" | ||
); | ||
} | ||
encodedLength = Buffer.alloc(2); | ||
encodedLength.writeUInt16LE(dataLen); | ||
break; | ||
default: | ||
throw new Error("Unsupported compact format"); | ||
} | ||
|
||
return Buffer.concat([encodedLength, data]); | ||
}; | ||
|
||
export { Header, FieldsV0, EncodingFormat, OpCode, encodeToBytes }; |
Oops, something went wrong.