-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add Bitcoin standard memo encoding example for bitSmiley and un…
…it tests (#196) Co-authored-by: Denis Fadeev <[email protected]>
- Loading branch information
1 parent
856177b
commit dbc21df
Showing
8 changed files
with
1,866 additions
and
24 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
var assert = require("assert"); | ||
const { | ||
Header, | ||
FieldsV0, | ||
EncodingFormat, | ||
OpCode, | ||
EncodeToBytes, | ||
} = require("./memo"); | ||
const { Web3 } = require("web3"); | ||
const web3 = new Web3(); | ||
|
||
// Test data | ||
const receiver = "0xEA9808f0Ac504d1F521B5BbdfC33e6f1953757a7"; | ||
const payload = new TextEncoder().encode("a payload"); | ||
const revertAddress = "tb1q6rufg6myrxurdn0h57d2qhtm9zfmjw2mzcm05q"; | ||
|
||
// Test case for memo ABI encoding | ||
const testMemoAbi = () => { | ||
// Create memo header | ||
const header = new Header( | ||
EncodingFormat.EncodingFmtABI, | ||
OpCode.DepositAndCall | ||
); | ||
|
||
// Create memo fields | ||
const fields = new FieldsV0(receiver, payload, revertAddress); | ||
|
||
// Encode standard memo | ||
const encodedMemo = EncodeToBytes(header, fields); | ||
const encodedMemoHex = web3.utils.bytesToHex(encodedMemo).slice(2); | ||
|
||
// Expected output | ||
const expectedHex = | ||
"5a001007" + // header | ||
"000000000000000000000000ea9808f0ac504d1f521b5bbdfc33e6f1953757a7" + // receiver | ||
"0000000000000000000000000000000000000000000000000000000000000060" + // payload offset | ||
"00000000000000000000000000000000000000000000000000000000000000a0" + // revertAddress offset | ||
"0000000000000000000000000000000000000000000000000000000000000009" + // payload length | ||
"61207061796c6f61640000000000000000000000000000000000000000000000" + // payload | ||
"000000000000000000000000000000000000000000000000000000000000002a" + // revertAddress length | ||
"746231713672756667366d7972787572646e3068353764327168746d397a666d6a77326d7a636d30357100000000000000000000000000000000000000000000"; // revertAddress | ||
|
||
// Compare with expected output | ||
assert.strictEqual( | ||
encodedMemoHex, | ||
expectedHex, | ||
"ABI encoding failed: encoded bytes do not match expected" | ||
); | ||
|
||
console.log("Test passed: testMemoAbi"); | ||
}; | ||
|
||
// Test case for memo compact short encoding | ||
const testMemoCompactShort = () => { | ||
// Create memo header | ||
const header = new Header( | ||
EncodingFormat.EncodingFmtCompactShort, | ||
OpCode.DepositAndCall | ||
); | ||
|
||
// Create memo fields | ||
const fields = new FieldsV0(receiver, payload, revertAddress); | ||
|
||
// Encode standard memo | ||
const encodedMemo = EncodeToBytes(header, fields); | ||
const encodedMemoHex = web3.utils.bytesToHex(encodedMemo).slice(2); | ||
|
||
// Expected output | ||
const expectedHex = | ||
"5a011007" + // header | ||
"ea9808f0ac504d1f521b5bbdfc33e6f1953757a7" + // receiver | ||
"0961207061796c6f6164" + // payload | ||
"2a746231713672756667366d7972787572646e3068353764327168746d397a666d6a77326d7a636d303571"; // revertAddress | ||
|
||
// Compare with expected output | ||
assert.strictEqual( | ||
encodedMemoHex, | ||
expectedHex, | ||
"Compact short encoding failed: encoded bytes do not match expected" | ||
); | ||
|
||
console.log("Test passed: testMemoCompactShort"); | ||
}; | ||
|
||
// Test case for memo compact long encoding | ||
const testMemoCompactLong = () => { | ||
// Create memo header | ||
const header = new Header( | ||
EncodingFormat.EncodingFmtCompactLong, | ||
OpCode.DepositAndCall | ||
); | ||
|
||
// Create memo fields | ||
const fields = new FieldsV0(receiver, payload, revertAddress); | ||
|
||
// Encode standard memo | ||
const encodedMemo = EncodeToBytes(header, fields); | ||
const encodedMemoHex = web3.utils.bytesToHex(encodedMemo).slice(2); | ||
|
||
// Expected output | ||
const expectedHex = | ||
"5a021007" + // header | ||
"ea9808f0ac504d1f521b5bbdfc33e6f1953757a7" + // receiver | ||
"090061207061796c6f6164" + // payload | ||
"2a00746231713672756667366d7972787572646e3068353764327168746d397a666d6a77326d7a636d303571"; // revertAddress | ||
|
||
// Compare with expected output | ||
assert.strictEqual( | ||
encodedMemoHex, | ||
expectedHex, | ||
"Compact long encoding failed: encoded bytes do not match expected" | ||
); | ||
|
||
console.log("Test passed: testMemoCompactLong"); | ||
}; | ||
|
||
// Run the test cases | ||
testMemoAbi(); | ||
testMemoCompactShort(); | ||
testMemoCompactLong(); |
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 |
---|---|---|
|
@@ -30,7 +30,8 @@ | |
"lint": "npm run lint:js && npm run lint:sol", | ||
"docs": "rimraf docs && typedoc ./packages/client/src/index.ts", | ||
"copy-templates": "npx cpx './packages/tasks/templates/**/*' ./dist/packages/tasks/templates", | ||
"copy-types": "npx cpx './typechain-types/**/*' ./dist/typechain-types" | ||
"copy-types": "npx cpx './typechain-types/**/*' ./dist/typechain-types", | ||
"test": "yarn run jest" | ||
}, | ||
"keywords": [], | ||
"author": "ZetaChain", | ||
|
@@ -46,6 +47,7 @@ | |
"@typechain/hardhat": "^6.1.2", | ||
"@types/chai": "^4.2.0", | ||
"@types/isomorphic-fetch": "^0.0.38", | ||
"@types/jest": "^29.5.14", | ||
"@types/lodash": "^4.14.202", | ||
"@types/mocha": ">=9.1.0", | ||
"@types/node": ">=12.0.0", | ||
|
@@ -67,12 +69,14 @@ | |
"eslint-plugin-typescript-sort-keys": "^2.3.0", | ||
"hardhat-gas-reporter": "^1.0.8", | ||
"http-server": "^14.1.1", | ||
"jest": "^29.7.0", | ||
"prettier": "^2.8.8", | ||
"prettier-plugin-solidity": "^1.1.3", | ||
"rimraf": "^5.0.1", | ||
"sinon": "^15.1.0", | ||
"solhint": "^3.4.1", | ||
"solidity-coverage": "^0.8.0", | ||
"ts-jest": "^29.2.5", | ||
"ts-node": ">=8.0.0", | ||
"typechain": "^8.1.0", | ||
"typedoc": "^0.26.5", | ||
|
@@ -106,7 +110,7 @@ | |
"dotenv": "16.0.3", | ||
"ecpair": "^2.1.0", | ||
"envfile": "^6.18.0", | ||
"ethers": "5.4.7", | ||
"ethers": "^5.4.7", | ||
"eventemitter3": "^5.0.1", | ||
"form-data": "^4.0.0", | ||
"handlebars": "4.7.7", | ||
|
@@ -117,7 +121,8 @@ | |
"ora": "5.4.1", | ||
"spinnies": "^0.5.1", | ||
"tiny-secp256k1": "^2.2.3", | ||
"web3": "^4.15.0", | ||
"ws": "^8.17.1" | ||
}, | ||
"packageManager": "[email protected]+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72" | ||
} | ||
} |
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 { ethers } from "ethers"; | ||
import { Web3 } from "web3"; | ||
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 { encodeToBytes, EncodingFormat, FieldsV0, Header, OpCode }; |
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
Oops, something went wrong.