Skip to content

Commit

Permalink
Merge branch 'ton-core-main'
Browse files Browse the repository at this point in the history
  • Loading branch information
dvlkv committed Oct 24, 2023
2 parents f4a7bad + e0ed819 commit 86e2f05
Show file tree
Hide file tree
Showing 14 changed files with 588 additions and 14 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.53.0] - 2023-10-24

## Added

- `Dictionary` `BitString` keys and values (thanks @Trinketer22)
- `BitString.isBitString` method (thanks @Trinketer22)
- `paddedBufferToBits` helper (thanks @Trinketer22)
- `Dictionary.generateMerkeProof` and `Dictionary.generateMerkleUpdate` (thanks @Gusarich)
- `OutAction` and `OutList` (de)serializers (thanks @siandreev)

## Fixed
- `BitString.substring` now accepts `offset` == `str.length`

## [0.52.2] - 2023-09-14

## Fixed
Expand Down
2 changes: 2 additions & 0 deletions src/address/Address.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ describe('Address', () => {
expect(address1.address.toRawString()).toBe('0:2cf55953e92efbeadab7ba725c3f93a0b23f842cbba72d7b8e6f510a70e422e3');
expect(address2.address.toRawString()).toBe('0:2cf55953e92efbeadab7ba725c3f93a0b23f842cbba72d7b8e6f510a70e422e3');
expect(address3.toRawString()).toBe('0:2cf55953e92efbeadab7ba725c3f93a0b23f842cbba72d7b8e6f510a70e422e3');
expect(address4.workChain).toBe(-1);
expect(address4.hash).toEqual(Buffer.from('3333333333333333333333333333333333333333333333333333333333333333', 'hex'));
});
it('should serialize to friendly form', () => {
let address = Address.parseRaw('0:2cf55953e92efbeadab7ba725c3f93a0b23f842cbba72d7b8e6f510a70e422e3');
Expand Down
48 changes: 47 additions & 1 deletion src/boc/BitString.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@ import { BitBuilder } from './BitBuilder';
import { BitString } from './BitString';

describe('BitString', () => {
let testOOB:(method: 'substring' | 'subbuffer' , bs:BitString, offset:number, length:number) => void;
beforeAll(() =>{
testOOB = (method: 'substring' | 'subbuffer', bs:BitString, offset:number, length: number) =>{
try {
let bs2 = bs[method](offset, length);
throw(Error("Should fail"));
}
catch(e: any) {
expect(e.message.endsWith('out of bounds')).toBe(true);
}
}
});

it('should read bits', () => {
let bs = new BitString(Buffer.from([0b10101010]), 0, 8);
expect(bs.at(0)).toBe(true);
Expand Down Expand Up @@ -47,6 +60,39 @@ describe('BitString', () => {
let bs2 = bs.subbuffer(0, 16);
expect(bs2!.length).toBe(2);
});
it('should do substrings', () => {
let bs = new BitString(Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]), 0, 64);
let bs2 = bs.substring(0, 16);
expect(bs2!.length).toBe(16);
});
it('should do empty substrings with requested length 0', () => {
let bs = new BitString(Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]), 0, 64);
let bs2 = bs.substring(bs.length, 0);
expect(bs2!.length).toBe(0);
});
it('should OOB when substring offset is out of bounds', () => {
let bs = new BitString(Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]), 0, 64);
testOOB('substring', bs, bs.length + 1, 0);
testOOB('substring', bs, -1, 0);
});
it('should OOB when subbuffer offset is out of bounds', () => {
let bs = new BitString(Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]), 0, 64);
testOOB('subbuffer', bs, bs.length + 1, 0);
testOOB('subbuffer', bs, -1, 0);
});
it('should OOB when offset is on the end of bitsring and length > 0', () =>{
let bs = new BitString(Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]), 0, 64);
testOOB('substring', bs, bs.length, 1);
});
it('should do empty subbuffers with requested length 0', () => {
let bs = new BitString(Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]), 0, 64);
let bs2 = bs.subbuffer(bs.length, 0);
expect(bs2!.length).toBe(0);
});
it('should OOB when offset is on the end of buffer and length > 0', () => {
let bs = new BitString(Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]), 0, 64);
testOOB('subbuffer', bs, bs.length, 1);
});
it('should process monkey strings', () => {
let cases = [
['001110101100111010', '3ACEA_'],
Expand Down Expand Up @@ -117,4 +163,4 @@ describe('BitString', () => {
expect(r.toString()).toEqual(c[1]);
}
});
});
});
13 changes: 10 additions & 3 deletions src/boc/BitString.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ export class BitString {
private readonly _length: number;
private readonly _data: Buffer;

/**
* Checks if supplied object is BitString
* @param src is unknow object
* @returns true if object is BitString and false otherwise
**/
static isBitString(src: unknown): src is BitString {
return src instanceof BitString;
}
/**
* Constructing BitString from a buffer
* @param data data that contains the bitstring data. NOTE: We are expecting this buffer to be NOT modified
Expand Down Expand Up @@ -82,7 +90,7 @@ export class BitString {
substring(offset: number, length: number) {

// Check offset
if (offset >= this._length) {
if (offset > this._length) {
throw new Error(`Offset(${offset}) > ${this._length} is out of bounds`);
}
if (offset < 0) {
Expand Down Expand Up @@ -111,7 +119,7 @@ export class BitString {
subbuffer(offset: number, length: number) {

// Check offset
if (offset >= this._length) {
if (offset > this._length) {
throw new Error(`Offset ${offset} is out of bounds`);
}
if (offset < 0) {
Expand Down Expand Up @@ -175,6 +183,5 @@ export class BitString {
}
}
}

[inspectSymbol] = () => this.toString()
}
24 changes: 23 additions & 1 deletion src/boc/utils/paddedBits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,26 @@ export function bitsToPaddedBuffer(bits: BitString) {
}

return builder.buffer();
}
}
export function paddedBufferToBits(buff: Buffer) {
let bitLen = 0;
// Finding rightmost non-zero byte in the buffer
for( let i = buff.length - 1; i >= 0; i--) {
if(buff[i] !== 0) {
const testByte = buff[i];
// Looking for a rightmost set padding bit
let bitPos = testByte & -testByte;
if((bitPos & 1) == 0) {
// It's power of 2 (only one bit set)
bitPos = Math.log2(bitPos) + 1;
}
if(i > 0){
// If we are dealing with more than 1 byte buffer
bitLen = i << 3; //Number of full bytes * 8
}
bitLen += 8 - bitPos;
break;
}
}
return new BitString(buff, 0, bitLen);
}
24 changes: 24 additions & 0 deletions src/dict/Dictionary.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { exoticMerkleProof } from "../boc/cell/exoticMerkleProof";
import { exoticMerkleUpdate } from "../boc/cell/exoticMerkleUpdate";
import { Dictionary } from "./Dictionary";
import fs from 'fs';
import { BitString } from "../boc/BitString";

function storeBits(builder: Builder, src: string) {
for (let s of src) {
Expand Down Expand Up @@ -86,6 +87,29 @@ describe('Dictionary', () => {
}
});

it('should parse dictionary with empty values', () => {
let cell = Cell.fromBoc(Buffer.from(fs.readFileSync(__dirname + "/__testdata__/empty_value.boc")))[0];
let testDict = Dictionary.loadDirect(Dictionary.Keys.BigUint(256), Dictionary.Values.BitString(0), cell);
expect(testDict.keys()[0]).toEqual(123n);
expect(testDict.get(123n)!.length).toBe(0);
});

it('should correctly serialize BitString keys and values', () => {
const keyLen = 9; // Not 8 bit aligned
const keys = Dictionary.Keys.BitString(keyLen);
const values = Dictionary.Values.BitString(72);
let testKey = new BitString(Buffer.from("Test"), 0, keyLen);
let testVal = new BitString(Buffer.from("BitString"), 0, 72);
let testDict = Dictionary.empty(keys, values);

testDict.set(testKey, testVal);
expect(testDict.get(testKey)!.equals(testVal)).toBe(true);

let serialized = beginCell().storeDictDirect(testDict).endCell();
let dictDs = Dictionary.loadDirect(keys, values, serialized);
expect(dictDs.get(testKey)!.equals(testVal)).toBe(true);
});

it('should generate merkle proofs', () => {
let d = Dictionary.empty(
Dictionary.Keys.Uint(8),
Expand Down
57 changes: 55 additions & 2 deletions src/dict/Dictionary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ import { Address } from "../address/Address";
import { beginCell, Builder } from "../boc/Builder";
import { Cell } from "../boc/Cell";
import { Slice } from "../boc/Slice";
import { BitString } from "../boc/BitString";
import { Maybe } from "../utils/maybe";
import { generateMerkleProof } from "./generateMerkleProof";
import { generateMerkleUpdate } from "./generateMerkleUpdate";
import { parseDict } from "./parseDict";
import { serializeDict } from "./serializeDict";
import { deserializeInternalKey, serializeInternalKey } from "./utils/internalKeySerializer";

export type DictionaryKeyTypes = Address | number | bigint | Buffer;
export type DictionaryKeyTypes = Address | number | bigint | Buffer | BitString;

export type DictionaryKey<K extends DictionaryKeyTypes> = {
bits: number;
Expand Down Expand Up @@ -84,6 +85,19 @@ export class Dictionary<K extends DictionaryKeyTypes, V> {
*/
Buffer: (bytes: number) => {
return createBufferKey(bytes);
},

/**
* Create BitString key
* @param bits key length
* @returns DictionaryKey<BitString>
* Point is that Buffer has to be 8 bit aligned,
* while key is TVM dictionary doesn't have to be
* aligned at all.
*/

BitString: (bits: number) => {
return createBitStringKey(bits);
}
}

Expand Down Expand Up @@ -174,6 +188,17 @@ export class Dictionary<K extends DictionaryKeyTypes, V> {
return createBufferValue(bytes);
},

/**
* Create BitString value
* @param requested bit length
* @returns DictionaryValue<BitString>
* Point is that Buffer is not applicable
* when length is not 8 bit alligned.
*/
BitString: (bits: number) => {
return createBitStringValue(bits);
},

/**
* Create dictionary value
* @param key
Expand Down Expand Up @@ -482,6 +507,20 @@ function createBufferKey(bytes: number): DictionaryKey<Buffer> {
}
}

function createBitStringKey(bits: number): DictionaryKey<BitString> {
return {
bits,
serialize: (src) => {
if(!BitString.isBitString(src))
throw Error('Key is not a BitString');
return beginCell().storeBits(src).endCell().beginParse().loadUintBig(bits);
},
parse: (src) => {
return beginCell().storeUint(src, bits).endCell().beginParse().loadBits(bits);
}
}
}

function createIntValue(bits: number): DictionaryValue<number> {
return {
serialize: (src, buidler) => {
Expand Down Expand Up @@ -604,4 +643,18 @@ function createBufferValue(size: number): DictionaryValue<Buffer> {
return src.loadBuffer(size);
}
}
}
}

function createBitStringValue(bits: number): DictionaryValue<BitString> {
return {
serialize: (src, builder) => {
if (src.length !== bits) {
throw Error('Invalid BitString size');
}
builder.storeBits(src);
},
parse: (src) => {
return src.loadBits(bits);
}
}
}
Binary file added src/dict/__testdata__/empty_value.boc
Binary file not shown.
13 changes: 12 additions & 1 deletion src/dict/utils/internalKeySerializer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import { Address } from "../../address/Address";
import { BitString } from "../../boc/BitString";
import { testAddress } from "../../utils/testAddress";
import { deserializeInternalKey, serializeInternalKey } from "./internalKeySerializer";

Expand Down Expand Up @@ -35,4 +36,14 @@ describe('internalKeySerializer', () => {
expect((deserializeInternalKey(serializeInternalKey(c)) as Buffer).equals(c)).toBe(true);
}
});
});
it('should serialize bit strings', () => {
let cs = [Buffer.from('00', 'hex'), Buffer.from('ff', 'hex'), Buffer.from('0f', 'hex'), Buffer.from('0f000011002233456611', 'hex')];
for (let c of cs) {
for(let i = 0; i < c.length * 8 - 1; i++) {
let bs = new BitString(c, 0, c.length * 8 - i);
const res = deserializeInternalKey(serializeInternalKey(bs)) as BitString;
expect(res.equals(bs)).toBe(true);
}
}
})
});
25 changes: 24 additions & 1 deletion src/dict/utils/internalKeySerializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
*/

import { Address } from "../../address/Address";
import { BitString } from "../../boc/BitString";
import { bitsToPaddedBuffer, paddedBufferToBits } from "../../boc/utils/paddedBits";

export function serializeInternalKey(value: any): string {
if (typeof value === 'number') {
Expand All @@ -20,6 +22,8 @@ export function serializeInternalKey(value: any): string {
return 'a:' + value.toString();
} else if (Buffer.isBuffer(value)) {
return 'f:' + value.toString('hex');
} else if(BitString.isBitString(value)) {
return 'B:' + value.toString();
} else {
throw Error('Invalid key type');
}
Expand All @@ -37,5 +41,24 @@ export function deserializeInternalKey(value: string): any {
} else if (k === 'f:') {
return Buffer.from(v, 'hex');
}
else if (k === 'B:') {

const lastDash = v.slice(-1) == "_";
const isPadded = lastDash || v.length % 2 != 0;
if(isPadded) {
let charLen = lastDash ? v.length - 1 : v.length;
const padded = v.substr(0, charLen) + "0"; //Padding
if((!lastDash) && ((charLen & 1) !== 0)){
// Four bit nibmle without padding
return new BitString(Buffer.from(padded, 'hex'), 0, charLen << 2);
}
else {
return paddedBufferToBits(Buffer.from(padded, 'hex'));
}
}
else {
return new BitString(Buffer.from(v, 'hex'), 0, v.length << 2);
}
}
throw Error('Invalid key type: ' + k);
}
}
Loading

0 comments on commit 86e2f05

Please sign in to comment.