2023-01-27 07:36:26 +03:00
import { defineProperties , concat , getBytesCopy , getNumber , hexlify , toBeArray , toBigInt , toNumber , assert , assertArgument } from "../../utils/index.js" ;
/ * *
* @ _ignore :
* /
2022-09-05 23:57:11 +03:00
export const WordSize = 32 ;
const Padding = new Uint8Array ( WordSize ) ;
// Properties used to immediate pass through to the underlying object
// - `then` is used to detect if an object is a Promise for await
const passProperties = [ "then" ] ;
const _guard = { } ;
2023-01-27 07:36:26 +03:00
function throwError ( name , error ) {
const wrapped = new Error ( ` deferred error during ABI decoding triggered accessing ${ name } ` ) ;
wrapped . error = error ;
throw wrapped ;
}
2022-11-30 23:44:23 +03:00
/ * *
* A [ [ Result ] ] is a sub - class of Array , which allows accessing any
* of its values either positionally by its index or , if keys are
* provided by its name .
*
* @ _docloc : api / abi
* /
2022-09-05 23:57:11 +03:00
export class Result extends Array {
2023-01-27 07:36:26 +03:00
# names ;
2022-11-30 23:44:23 +03:00
/ * *
* @ private
* /
2023-01-27 07:36:26 +03:00
constructor ( ... args ) {
// To properly sub-class Array so the other built-in
// functions work, the constructor has to behave fairly
// well. So, in the event we are created via fromItems()
// we build the read-only Result object we want, but on
// any other input, we use the default constructor
// constructor(guard: any, items: Array<any>, keys?: Array<null | string>);
const guard = args [ 0 ] ;
let items = args [ 1 ] ;
let names = ( args [ 2 ] || [ ] ) . slice ( ) ;
let wrap = true ;
if ( guard !== _guard ) {
items = args ;
names = [ ] ;
wrap = false ;
2022-09-05 23:57:11 +03:00
}
2023-01-27 07:36:26 +03:00
// Can't just pass in ...items since an array of length 1
// is a special case in the super.
super ( items . length ) ;
items . forEach ( ( item , index ) => { this [ index ] = item ; } ) ;
// Find all unique keys
const nameCounts = names . reduce ( ( accum , name ) => {
if ( typeof ( name ) === "string" ) {
accum . set ( name , ( accum . get ( name ) || 0 ) + 1 ) ;
}
return accum ;
} , ( new Map ( ) ) ) ;
// Remove any key thats not unique
this . # names = Object . freeze ( items . map ( ( item , index ) => {
const name = names [ index ] ;
if ( name != null && nameCounts . get ( name ) === 1 ) {
return name ;
}
return null ;
} ) ) ;
if ( ! wrap ) {
return ;
}
// A wrapped Result is immutable
2022-09-05 23:57:11 +03:00
Object . freeze ( this ) ;
2023-01-27 07:36:26 +03:00
// Proxy indices and names so we can trap deferred errors
2022-09-05 23:57:11 +03:00
return new Proxy ( this , {
get : ( target , prop , receiver ) => {
if ( typeof ( prop ) === "string" ) {
2023-01-27 07:36:26 +03:00
// Index accessor
2022-09-05 23:57:11 +03:00
if ( prop . match ( /^[0-9]+$/ ) ) {
2022-09-16 05:58:45 +03:00
const index = getNumber ( prop , "%index" ) ;
2022-09-05 23:57:11 +03:00
if ( index < 0 || index >= this . length ) {
throw new RangeError ( "out of result range" ) ;
}
const item = target [ index ] ;
if ( item instanceof Error ) {
2023-01-27 07:36:26 +03:00
throwError ( ` index ${ index } ` , item ) ;
2022-09-05 23:57:11 +03:00
}
return item ;
}
// Pass important checks (like `then` for Promise) through
2023-01-27 07:36:26 +03:00
if ( passProperties . indexOf ( prop ) >= 0 ) {
2022-09-05 23:57:11 +03:00
return Reflect . get ( target , prop , receiver ) ;
}
2023-01-27 07:36:26 +03:00
const value = target [ prop ] ;
if ( value instanceof Function ) {
// Make sure functions work with private variables
// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy#no_private_property_forwarding
return function ( ... args ) {
return value . apply ( ( this === receiver ) ? target : this , args ) ;
} ;
}
else if ( ! ( prop in target ) ) {
// Possible name accessor
return target . getValue . apply ( ( this === receiver ) ? target : this , [ prop ] ) ;
2022-09-05 23:57:11 +03:00
}
}
return Reflect . get ( target , prop , receiver ) ;
}
} ) ;
}
2023-01-27 07:36:26 +03:00
/ * *
2024-04-10 04:13:01 +03:00
* Returns the Result as a normal Array . If % % deep % % , any children
* which are Result objects are also converted to a normal Array .
2023-01-27 07:36:26 +03:00
*
* This will throw if there are any outstanding deferred
* errors .
* /
2024-04-10 04:13:01 +03:00
toArray ( deep ) {
2023-02-19 06:18:42 +03:00
const result = [ ] ;
2023-01-27 07:36:26 +03:00
this . forEach ( ( item , index ) => {
if ( item instanceof Error ) {
throwError ( ` index ${ index } ` , item ) ;
2022-09-05 23:57:11 +03:00
}
2024-04-10 04:13:01 +03:00
if ( deep && item instanceof Result ) {
item = item . toArray ( deep ) ;
}
2023-02-19 06:18:42 +03:00
result . push ( item ) ;
2023-01-27 07:36:26 +03:00
} ) ;
2023-02-19 06:18:42 +03:00
return result ;
2023-01-27 07:36:26 +03:00
}
/ * *
2024-04-10 04:13:01 +03:00
* Returns the Result as an Object with each name - value pair . If
* % % deep % % , any children which are Result objects are also
* converted to an Object .
2023-01-27 07:36:26 +03:00
*
* This will throw if any value is unnamed , or if there are
* any outstanding deferred errors .
* /
2024-04-10 04:13:01 +03:00
toObject ( deep ) {
2023-01-27 07:36:26 +03:00
return this . # names . reduce ( ( accum , name , index ) => {
assert ( name != null , "value at index ${ index } unnamed" , "UNSUPPORTED_OPERATION" , {
operation : "toObject()"
} ) ;
// Add values for names that don't conflict
if ( ! ( name in accum ) ) {
2024-04-10 04:13:01 +03:00
let child = this . getValue ( name ) ;
if ( deep && child instanceof Result ) {
child = child . toObject ( deep ) ;
}
accum [ name ] = child ;
2023-01-27 07:36:26 +03:00
}
return accum ;
} , { } ) ;
2022-09-05 23:57:11 +03:00
}
2022-11-30 23:44:23 +03:00
/ * *
* @ _ignore
* /
2022-09-05 23:57:11 +03:00
slice ( start , end ) {
if ( start == null ) {
start = 0 ;
}
2023-02-19 06:18:42 +03:00
if ( start < 0 ) {
start += this . length ;
if ( start < 0 ) {
start = 0 ;
}
}
2022-09-05 23:57:11 +03:00
if ( end == null ) {
end = this . length ;
}
2023-02-19 06:18:42 +03:00
if ( end < 0 ) {
end += this . length ;
if ( end < 0 ) {
end = 0 ;
}
}
if ( end > this . length ) {
end = this . length ;
}
2023-01-27 07:36:26 +03:00
const result = [ ] , names = [ ] ;
2022-09-05 23:57:11 +03:00
for ( let i = start ; i < end ; i ++ ) {
2023-01-27 07:36:26 +03:00
result . push ( this [ i ] ) ;
names . push ( this . # names [ i ] ) ;
}
return new Result ( _guard , result , names ) ;
}
/ * *
* @ _ignore
* /
filter ( callback , thisArg ) {
const result = [ ] , names = [ ] ;
for ( let i = 0 ; i < this . length ; i ++ ) {
const item = this [ i ] ;
if ( item instanceof Error ) {
throwError ( ` index ${ i } ` , item ) ;
2022-09-05 23:57:11 +03:00
}
2023-01-27 07:36:26 +03:00
if ( callback . call ( thisArg , item , i , this ) ) {
result . push ( item ) ;
names . push ( this . # names [ i ] ) ;
2022-09-05 23:57:11 +03:00
}
}
2023-01-27 07:36:26 +03:00
return new Result ( _guard , result , names ) ;
2022-09-05 23:57:11 +03:00
}
2023-05-19 00:26:59 +03:00
/ * *
* @ _ignore
* /
map ( callback , thisArg ) {
const result = [ ] ;
for ( let i = 0 ; i < this . length ; i ++ ) {
const item = this [ i ] ;
if ( item instanceof Error ) {
throwError ( ` index ${ i } ` , item ) ;
}
result . push ( callback . call ( thisArg , item , i , this ) ) ;
}
return result ;
}
2022-11-30 23:44:23 +03:00
/ * *
* Returns the value for % % name % % .
*
* Since it is possible to have a key whose name conflicts with
* a method on a [ [ Result ] ] or its superclass Array , or any
* JavaScript keyword , this ensures all named values are still
* accessible by name .
* /
2022-09-05 23:57:11 +03:00
getValue ( name ) {
2023-01-27 07:36:26 +03:00
const index = this . # names . indexOf ( name ) ;
if ( index === - 1 ) {
return undefined ;
}
const value = this [ index ] ;
if ( value instanceof Error ) {
throwError ( ` property ${ JSON . stringify ( name ) } ` , value . error ) ;
2022-09-05 23:57:11 +03:00
}
2023-01-27 07:36:26 +03:00
return value ;
2022-09-05 23:57:11 +03:00
}
2022-11-30 23:44:23 +03:00
/ * *
* Creates a new [ [ Result ] ] for % % items % % with each entry
* also accessible by its corresponding name in % % keys % % .
* /
2022-09-05 23:57:11 +03:00
static fromItems ( items , keys ) {
return new Result ( _guard , items , keys ) ;
}
}
2022-11-30 23:44:23 +03:00
/ * *
* Returns all errors found in a [ [ Result ] ] .
*
* Since certain errors encountered when creating a [ [ Result ] ] do
* not impact the ability to continue parsing data , they are
* deferred until they are actually accessed . Hence a faulty string
* in an Event that is never used does not impact the program flow .
*
* However , sometimes it may be useful to access , identify or
* validate correctness of a [ [ Result ] ] .
*
* @ _docloc api / abi
* /
2022-09-05 23:57:11 +03:00
export function checkResultErrors ( result ) {
// Find the first error (if any)
const errors = [ ] ;
const checkErrors = function ( path , object ) {
if ( ! Array . isArray ( object ) ) {
return ;
}
for ( let key in object ) {
const childPath = path . slice ( ) ;
childPath . push ( key ) ;
try {
checkErrors ( childPath , object [ key ] ) ;
}
catch ( error ) {
errors . push ( { path : childPath , error : error } ) ;
}
}
} ;
checkErrors ( [ ] , result ) ;
return errors ;
}
function getValue ( value ) {
2022-12-10 02:24:58 +03:00
let bytes = toBeArray ( value ) ;
2022-11-09 10:57:02 +03:00
assert ( bytes . length <= WordSize , "value out-of-bounds" , "BUFFER_OVERRUN" , { buffer : bytes , length : WordSize , offset : bytes . length } ) ;
2022-09-05 23:57:11 +03:00
if ( bytes . length !== WordSize ) {
2022-09-16 05:58:45 +03:00
bytes = getBytesCopy ( concat ( [ Padding . slice ( bytes . length % WordSize ) , bytes ] ) ) ;
2022-09-05 23:57:11 +03:00
}
return bytes ;
}
2022-11-30 23:44:23 +03:00
/ * *
* @ _ignore
* /
2022-09-05 23:57:11 +03:00
export class Coder {
// The coder name:
// - address, uint256, tuple, array, etc.
name ;
// The fully expanded type, including composite types:
// - address, uint256, tuple(address,bytes), uint256[3][4][], etc.
type ;
// The localName bound in the signature, in this example it is "baz":
// - tuple(address foo, uint bar) baz
localName ;
// Whether this type is dynamic:
// - Dynamic: bytes, string, address[], tuple(boolean[]), etc.
// - Not Dynamic: address, uint256, boolean[3], tuple(address, uint8)
dynamic ;
constructor ( name , type , localName , dynamic ) {
defineProperties ( this , { name , type , localName , dynamic } , {
name : "string" , type : "string" , localName : "string" , dynamic : "boolean"
} ) ;
}
_throwError ( message , value ) {
2022-11-09 10:57:02 +03:00
assertArgument ( false , message , this . localName , value ) ;
2022-09-05 23:57:11 +03:00
}
}
2022-11-30 23:44:23 +03:00
/ * *
* @ _ignore
* /
2022-09-05 23:57:11 +03:00
export class Writer {
// An array of WordSize lengthed objects to concatenation
# data ;
# dataLength ;
constructor ( ) {
this . # data = [ ] ;
this . # dataLength = 0 ;
}
get data ( ) {
return concat ( this . # data ) ;
}
get length ( ) { return this . # dataLength ; }
# writeData ( data ) {
this . # data . push ( data ) ;
this . # dataLength += data . length ;
return data . length ;
}
appendWriter ( writer ) {
2022-09-16 05:58:45 +03:00
return this . # writeData ( getBytesCopy ( writer . data ) ) ;
2022-09-05 23:57:11 +03:00
}
// Arrayish item; pad on the right to *nearest* WordSize
writeBytes ( value ) {
2022-09-16 05:58:45 +03:00
let bytes = getBytesCopy ( value ) ;
2022-09-05 23:57:11 +03:00
const paddingOffset = bytes . length % WordSize ;
if ( paddingOffset ) {
2022-09-16 05:58:45 +03:00
bytes = getBytesCopy ( concat ( [ bytes , Padding . slice ( paddingOffset ) ] ) ) ;
2022-09-05 23:57:11 +03:00
}
return this . # writeData ( bytes ) ;
}
// Numeric item; pad on the left *to* WordSize
writeValue ( value ) {
return this . # writeData ( getValue ( value ) ) ;
}
// Inserts a numeric place-holder, returning a callback that can
// be used to asjust the value later
writeUpdatableValue ( ) {
const offset = this . # data . length ;
this . # data . push ( Padding ) ;
this . # dataLength += WordSize ;
return ( value ) => {
this . # data [ offset ] = getValue ( value ) ;
} ;
}
}
2022-11-30 23:44:23 +03:00
/ * *
* @ _ignore
* /
2022-09-05 23:57:11 +03:00
export class Reader {
// Allows incomplete unpadded data to be read; otherwise an error
// is raised if attempting to overrun the buffer. This is required
// to deal with an old Solidity bug, in which event data for
// external (not public thoguh) was tightly packed.
allowLoose ;
# data ;
# offset ;
2024-01-13 03:47:16 +03:00
# bytesRead ;
# parent ;
# maxInflation ;
constructor ( data , allowLoose , maxInflation ) {
2022-09-05 23:57:11 +03:00
defineProperties ( this , { allowLoose : ! ! allowLoose } ) ;
2022-09-16 05:58:45 +03:00
this . # data = getBytesCopy ( data ) ;
2024-01-13 03:47:16 +03:00
this . # bytesRead = 0 ;
this . # parent = null ;
this . # maxInflation = ( maxInflation != null ) ? maxInflation : 1024 ;
2022-09-05 23:57:11 +03:00
this . # offset = 0 ;
}
get data ( ) { return hexlify ( this . # data ) ; }
get dataLength ( ) { return this . # data . length ; }
get consumed ( ) { return this . # offset ; }
get bytes ( ) { return new Uint8Array ( this . # data ) ; }
2024-01-13 03:47:16 +03:00
# incrementBytesRead ( count ) {
if ( this . # parent ) {
return this . # parent . # incrementBytesRead ( count ) ;
}
this . # bytesRead += count ;
// Check for excessive inflation (see: #4537)
assert ( this . # maxInflation < 1 || this . # bytesRead <= this . # maxInflation * this . dataLength , ` compressed ABI data exceeds inflation ratio of ${ this . # maxInflation } ( see: https:/ \/ github.com/ethers-io/ethers.js/issues/4537 ) ` , "BUFFER_OVERRUN" , {
buffer : getBytesCopy ( this . # data ) , offset : this . # offset ,
length : count , info : {
bytesRead : this . # bytesRead ,
dataLength : this . dataLength
}
} ) ;
}
2022-09-05 23:57:11 +03:00
# peekBytes ( offset , length , loose ) {
let alignedLength = Math . ceil ( length / WordSize ) * WordSize ;
if ( this . # offset + alignedLength > this . # data . length ) {
if ( this . allowLoose && loose && this . # offset + length <= this . # data . length ) {
alignedLength = length ;
}
else {
2022-11-09 10:57:02 +03:00
assert ( false , "data out-of-bounds" , "BUFFER_OVERRUN" , {
2022-09-16 05:58:45 +03:00
buffer : getBytesCopy ( this . # data ) ,
2022-09-05 23:57:11 +03:00
length : this . # data . length ,
offset : this . # offset + alignedLength
} ) ;
}
}
return this . # data . slice ( this . # offset , this . # offset + alignedLength ) ;
}
// Create a sub-reader with the same underlying data, but offset
subReader ( offset ) {
2024-01-13 03:47:16 +03:00
const reader = new Reader ( this . # data . slice ( this . # offset + offset ) , this . allowLoose , this . # maxInflation ) ;
reader . # parent = this ;
return reader ;
2022-09-05 23:57:11 +03:00
}
// Read bytes
readBytes ( length , loose ) {
let bytes = this . # peekBytes ( 0 , length , ! ! loose ) ;
2024-01-13 03:47:16 +03:00
this . # incrementBytesRead ( length ) ;
2022-09-05 23:57:11 +03:00
this . # offset += bytes . length ;
// @TODO: Make sure the length..end bytes are all 0?
return bytes . slice ( 0 , length ) ;
}
// Read a numeric values
readValue ( ) {
return toBigInt ( this . readBytes ( WordSize ) ) ;
}
readIndex ( ) {
return toNumber ( this . readBytes ( WordSize ) ) ;
}
}
//# sourceMappingURL=abstract-coder.js.map