-
Notifications
You must be signed in to change notification settings - Fork 153
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
21 changed files
with
995 additions
and
1,140 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,7 @@ | ||
{ | ||
"trailingComma": "all", | ||
"tabWidth": 2, | ||
"semi": false, | ||
"singleQuote": true, | ||
"printWidth": 120 | ||
} |
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
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 |
---|---|---|
@@ -1,78 +1,62 @@ | ||
import { Codec, Struct, StructTypes } from "../codec"; | ||
import { Sink } from "../sink"; | ||
import { slotsCount } from "../utils"; | ||
import { Src } from "../src"; | ||
import assert from "node:assert"; | ||
|
||
type FunctionReturn<T> = T extends Codec<infer U> | ||
? U | ||
: T extends Struct | ||
? StructTypes<T> | ||
: undefined; | ||
|
||
export class AbiFunction< | ||
const T extends Struct, | ||
const R extends Codec<any> | Struct | undefined | ||
> { | ||
readonly #selector: Buffer; | ||
private readonly slotsCount: number; | ||
|
||
constructor( | ||
public selector: string, | ||
public readonly args: T, | ||
public readonly returnType?: R | ||
) { | ||
assert(selector.startsWith("0x"), "selector must start with 0x"); | ||
assert(selector.length === 10, "selector must be 4 bytes long"); | ||
this.#selector = Buffer.from(selector.slice(2), "hex"); | ||
this.args = args; | ||
this.slotsCount = slotsCount(Object.values(args)); | ||
import { Codec, Struct, StructTypes } from '../codec' | ||
import { Sink } from '../sink' | ||
import { slotsCount } from '../utils' | ||
import { Src } from '../src' | ||
import assert from 'node:assert' | ||
|
||
type FunctionReturn<T> = T extends Codec<infer U> ? U : T extends Struct ? StructTypes<T> : undefined | ||
|
||
export class AbiFunction<const T extends Struct, const R extends Codec<any> | Struct | undefined> { | ||
readonly #selector: Buffer | ||
private readonly slotsCount: number | ||
|
||
constructor(public selector: string, public readonly args: T, public readonly returnType?: R) { | ||
assert(selector.startsWith('0x'), 'selector must start with 0x') | ||
assert(selector.length === 10, 'selector must be 4 bytes long') | ||
this.#selector = Buffer.from(selector.slice(2), 'hex') | ||
this.args = args | ||
this.slotsCount = slotsCount(Object.values(args)) | ||
} | ||
|
||
is(calldata: string) { | ||
return calldata.startsWith(this.selector); | ||
return calldata.startsWith(this.selector) | ||
} | ||
|
||
encode(args: StructTypes<T>) { | ||
const sink = new Sink(this.slotsCount); | ||
const sink = new Sink(this.slotsCount) | ||
for (let i in this.args) { | ||
this.args[i].encode(sink, args[i]); | ||
this.args[i].encode(sink, args[i]) | ||
} | ||
return `0x${Buffer.concat([this.#selector, sink.result()]).toString( | ||
"hex" | ||
)}`; | ||
return `0x${Buffer.concat([this.#selector, sink.result()]).toString('hex')}` | ||
} | ||
|
||
decode(calldata: string): StructTypes<T> { | ||
assert( | ||
this.is(calldata), | ||
`unexpected function signature: ${calldata.slice(0, 10)}` | ||
); | ||
const src = new Src(Buffer.from(calldata.slice(10), "hex")); | ||
const result = {} as any; | ||
assert(this.is(calldata), `unexpected function signature: ${calldata.slice(0, 10)}`) | ||
const src = new Src(Buffer.from(calldata.slice(10), 'hex')) | ||
const result = {} as any | ||
for (let i in this.args) { | ||
result[i] = this.args[i].decode(src); | ||
result[i] = this.args[i].decode(src) | ||
} | ||
return result; | ||
return result | ||
} | ||
|
||
private isCodecs(value: any): value is Codec<any> { | ||
return "decode" in value && "encode" in value; | ||
return 'decode' in value && 'encode' in value | ||
} | ||
|
||
decodeResult(output: string): FunctionReturn<R> { | ||
if (!this.returnType) { | ||
return undefined as any; | ||
return undefined as any | ||
} | ||
const src = new Src(Buffer.from(output.slice(2), "hex")); | ||
const src = new Src(Buffer.from(output.slice(2), 'hex')) | ||
if (this.isCodecs(this.returnType)) { | ||
return this.returnType.decode(src) as any; | ||
return this.returnType.decode(src) as any | ||
} | ||
const result = {} as any; | ||
const result = {} as any | ||
for (let i in this.returnType) { | ||
const codec = this.returnType[i] as Codec<any>; | ||
result[i] = codec.decode(src); | ||
const codec = this.returnType[i] as Codec<any> | ||
result[i] = codec.decode(src) | ||
} | ||
return result; | ||
return result | ||
} | ||
} |
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 |
---|---|---|
@@ -1,20 +1,20 @@ | ||
import type { Sink } from "./sink"; | ||
import type { Src } from "./src"; | ||
import type { Pretty } from "./utils"; | ||
import type { Sink } from './sink' | ||
import type { Src } from './src' | ||
import type { Pretty } from './utils' | ||
|
||
export const WORD_SIZE = 32; | ||
export const WORD_SIZE = 32 | ||
|
||
export interface Codec<T> { | ||
encode(sink: Sink, val: T): void; | ||
decode(src: Src): T; | ||
isDynamic: boolean; | ||
slotsCount?: number; | ||
encode(sink: Sink, val: T): void | ||
decode(src: Src): T | ||
isDynamic: boolean | ||
slotsCount?: number | ||
} | ||
|
||
export type Struct = { | ||
[key: string]: Codec<any>; | ||
}; | ||
[key: string]: Codec<any> | ||
} | ||
|
||
export type StructTypes<T extends Struct> = Pretty<{ | ||
[K in keyof T]: T[K] extends Codec<infer U> ? U : never; | ||
}>; | ||
[K in keyof T]: T[K] extends Codec<infer U> ? U : never | ||
}> |
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 |
---|---|---|
@@ -1,85 +1,83 @@ | ||
import { Codec, WORD_SIZE } from "../codec"; | ||
import { Sink } from "../sink"; | ||
import { Src } from "../src"; | ||
import { Codec, WORD_SIZE } from '../codec' | ||
import { Sink } from '../sink' | ||
import { Src } from '../src' | ||
|
||
export class ArrayCodec<const T> implements Codec<readonly T[]> { | ||
public readonly isDynamic = true; | ||
public readonly isDynamic = true | ||
|
||
constructor(public readonly item: Codec<T>) {} | ||
|
||
encode(sink: Sink, val: T[]) { | ||
sink.dynamicOffset(val.length); | ||
sink.dynamicOffset(val.length) | ||
for (let i = 0; i < val.length; i++) { | ||
this.item.encode(sink, val[i]); | ||
this.item.encode(sink, val[i]) | ||
} | ||
sink.increaseSize(WORD_SIZE); | ||
sink.endDynamic(); | ||
sink.increaseSize(WORD_SIZE) | ||
sink.endDynamic() | ||
} | ||
|
||
decode(src: Src): T[] { | ||
const offset = src.u32(); | ||
const offset = src.u32() | ||
|
||
src.safeJump(offset); | ||
const len = src.u32(); | ||
src.safeJump(offset) | ||
const len = src.u32() | ||
|
||
const tmpSrc = src.slice(offset + WORD_SIZE); | ||
const val = new Array(len); | ||
const tmpSrc = src.slice(offset + WORD_SIZE) | ||
const val = new Array(len) | ||
for (let i = 0; i < val.length; i++) { | ||
val[i] = this.item.decode(tmpSrc); | ||
val[i] = this.item.decode(tmpSrc) | ||
} | ||
src.jumpBack(); | ||
return val; | ||
src.jumpBack() | ||
return val | ||
} | ||
} | ||
|
||
export class FixedSizeArrayCodec<const T> implements Codec<readonly T[]> { | ||
public isDynamic: boolean; | ||
public slotsCount: number; | ||
public isDynamic: boolean | ||
public slotsCount: number | ||
constructor(public readonly item: Codec<T>, public readonly size: number) { | ||
this.isDynamic = item.isDynamic && size > 0; | ||
this.slotsCount = this.isDynamic ? 1 : size; | ||
this.isDynamic = item.isDynamic && size > 0 | ||
this.slotsCount = this.isDynamic ? 1 : size | ||
} | ||
|
||
encode(sink: Sink, val: T[]) { | ||
if (val.length !== this.size) { | ||
throw new Error( | ||
`invalid array length: ${val.length}. Expected: ${this.size}` | ||
); | ||
throw new Error(`invalid array length: ${val.length}. Expected: ${this.size}`) | ||
} | ||
if (this.isDynamic) { | ||
return this.encodeDynamic(sink, val); | ||
return this.encodeDynamic(sink, val) | ||
} | ||
for (let i = 0; i < this.size; i++) { | ||
this.item.encode(sink, val[i]); | ||
this.item.encode(sink, val[i]) | ||
} | ||
} | ||
|
||
private encodeDynamic(sink: Sink, val: T[]) { | ||
sink.offset(this.size); | ||
sink.offset(this.size) | ||
for (let i = 0; i < val.length; i++) { | ||
this.item.encode(sink, val[i]); | ||
this.item.encode(sink, val[i]) | ||
} | ||
sink.endDynamic(); | ||
sink.endDynamic() | ||
} | ||
|
||
decode(src: Src): T[] { | ||
if (this.isDynamic) { | ||
return this.decodeDynamic(src); | ||
return this.decodeDynamic(src) | ||
} | ||
let val = new Array(this.size); | ||
let val = new Array(this.size) | ||
for (let i = 0; i < val.length; i++) { | ||
val[i] = this.item.decode(src); | ||
val[i] = this.item.decode(src) | ||
} | ||
return val; | ||
return val | ||
} | ||
|
||
private decodeDynamic(src: Src): T[] { | ||
const offset = src.u32(); | ||
const tmpSrc = src.slice(offset); | ||
let val = new Array(this.size); | ||
const offset = src.u32() | ||
const tmpSrc = src.slice(offset) | ||
let val = new Array(this.size) | ||
for (let i = 0; i < val.length; i++) { | ||
val[i] = this.item.decode(tmpSrc); | ||
val[i] = this.item.decode(tmpSrc) | ||
} | ||
return val; | ||
return val | ||
} | ||
} |
Oops, something went wrong.