aboutsummaryrefslogblamecommitdiffstats
path: root/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts
blob: 00059a4b62050556f037bd700712d395b387eb9f (plain) (tree)
1
2
3
4
5
6
7
8
9



                                           
                                                          


                                                              
                                                  
                                                  
 

                                                                   
 
                                                    
 
                                                            

                                                             
                                                           










                                          
                                     



                                                  
                                                     
                                                                                                            
                              
                                                                                             


         
                                                                                                        








                                                                                       
                                                                                                 
                                                                                             
                                                                

                                                                       
                                                                                  

                                                                                                  
                                                                                              

                                  
                                                                    
                                                                          
                       
                                                                           




                                                                          
                                                                         





                                                                          
                                                    




                                
                                                             
                                                                

                         
                                                                            
                                                                             
                                                             
           
                                                        


                        
                                                                                                            
                                                                                                
                                                                                      





                                                                                      
                                                    
                                                                                   
                                                                                                     
                                                                                                
                                    
                                                                
                                                                                        
                                                 

                                                                                   
              
                                    
         
                                            
                                                         
                                                            

                                                                                
           

                                               

     
                                                                                                            
                                                    
                                                                                   
                                                                                                     
                                            
                                                         
                                                              





                                                                                                                       

                                                                                                                

                                 
                                                            


                                                                                                       


                                                  














                                                                
                                                                                         
                       
                                                 




                                                                                                                      
         
                                                             
                                       
                                                        





                                                               
                                             


                                                                        
                                                                

                                
                                            

     
                                                                                                           
                                                                     
                                       
                                                        

                                        
                                              
                                                                                          
                                                                              

                                                   
                                             
                                                       
             
                                                                              
                                                                                 
                                     
           
                                            

     
import { DataItem } from 'ethereum-types';
import * as ethUtil from 'ethereumjs-util';
import * as _ from 'lodash';

import { BigNumber } from '../../../configured_bignumber';
import { SetCalldataBlock } from '../../calldata/blocks/set';
import { CalldataBlock } from '../../calldata/calldata_block';
import { RawCalldata } from '../../calldata/raw_calldata';
import { constants } from '../../utils/constants';
import { DecodingRules } from '../../utils/rules';

import { DataType } from '../data_type';
import { DataTypeFactory, MemberIndexByName } from '../interfaces';

import { AbstractPointerDataType } from './pointer';

export abstract class AbstractSetDataType extends DataType {
    protected readonly _arrayLength: number | undefined;
    protected readonly _arrayElementType: string | undefined;
    private readonly _memberIndexByName: MemberIndexByName;
    private readonly _members: DataType[];
    private readonly _isArray: boolean;

    public constructor(
        dataItem: DataItem,
        factory: DataTypeFactory,
        isArray: boolean = false,
        arrayLength?: number,
        arrayElementType?: string,
    ) {
        super(dataItem, factory);
        this._memberIndexByName = {};
        this._members = [];
        this._isArray = isArray;
        this._arrayLength = arrayLength;
        this._arrayElementType = arrayElementType;
        if (isArray && !_.isUndefined(arrayLength)) {
            [this._members, this._memberIndexByName] = this._createMembersWithLength(dataItem, arrayLength);
        } else if (!isArray) {
            [this._members, this._memberIndexByName] = this._createMembersWithKeys(dataItem);
        }
    }

    public generateCalldataBlock(value: any[] | object, parentBlock?: CalldataBlock): SetCalldataBlock {
        const block =
            value instanceof Array
                ? this._generateCalldataBlockFromArray(value, parentBlock)
                : this._generateCalldataBlockFromObject(value, parentBlock);
        return block;
    }

    public generateValue(calldata: RawCalldata, rules: DecodingRules): any[] | object {
        let members = this._members;
        // Case 1: This is an array of undefined length, which means that `this._members` was not
        //         populated in the constructor. So we must construct the set of members now.
        if (this._isArray && _.isUndefined(this._arrayLength)) {
            const arrayLengthBuf = calldata.popWord();
            const arrayLengthHex = ethUtil.bufferToHex(arrayLengthBuf);
            const arrayLength = new BigNumber(arrayLengthHex, constants.HEX_BASE);
            [members] = this._createMembersWithLength(this.getDataItem(), arrayLength.toNumber());
        }
        // Create a new scope in the calldata, before descending into the members of this set.
        calldata.startScope();
        let value: any[] | object;
        if (rules.shouldConvertStructsToObjects && !this._isArray) {
            // Construct an object with values for each member of the set.
            value = {};
            _.each(this._memberIndexByName, (idx: number, key: string) => {
                const member = this._members[idx];
                const memberValue = member.generateValue(calldata, rules);
                (value as { [key: string]: any })[key] = memberValue;
            });
        } else {
            // Construct an array with values for each member of the set.
            value = [];
            _.each(members, (member: DataType, idx: number) => {
                const memberValue = member.generateValue(calldata, rules);
                (value as any[]).push(memberValue);
            });
        }
        // Close this scope and return tetheh value.
        calldata.endScope();
        return value;
    }

    public isStatic(): boolean {
        // An array with an undefined length is never static.
        if (this._isArray && _.isUndefined(this._arrayLength)) {
            return false;
        }
        // If any member of the set is a pointer then the set is not static.
        const dependentMember = _.find(this._members, (member: DataType) => {
            return member instanceof AbstractPointerDataType;
        });
        const isStatic = _.isUndefined(dependentMember);
        return isStatic;
    }

    protected _generateCalldataBlockFromArray(value: any[], parentBlock?: CalldataBlock): SetCalldataBlock {
        // Sanity check: if the set has a defined length then `value` must have the same length.
        if (!_.isUndefined(this._arrayLength) && value.length !== this._arrayLength) {
            throw new Error(
                `Expected array of ${JSON.stringify(
                    this._arrayLength,
                )} elements, but got array of length ${JSON.stringify(value.length)}`,
            );
        }
        // Create a new calldata block for this set.
        const parentName = _.isUndefined(parentBlock) ? '' : parentBlock.getName();
        const block = new SetCalldataBlock(this.getDataItem().name, this.getSignature(), parentName);
        // If this set has an undefined length then set its header to be the number of elements.
        let members = this._members;
        if (this._isArray && _.isUndefined(this._arrayLength)) {
            [members] = this._createMembersWithLength(this.getDataItem(), value.length);
            const lenBuf = ethUtil.setLengthLeft(
                ethUtil.toBuffer(`0x${value.length.toString(constants.HEX_BASE)}`),
                constants.EVM_WORD_WIDTH_IN_BYTES,
            );
            block.setHeader(lenBuf);
        }
        // Create blocks for members of set.
        const memberCalldataBlocks: CalldataBlock[] = [];
        _.each(members, (member: DataType, idx: number) => {
            const memberBlock = member.generateCalldataBlock(value[idx], block);
            memberCalldataBlocks.push(memberBlock);
        });
        block.setMembers(memberCalldataBlocks);
        return block;
    }

    protected _generateCalldataBlockFromObject(obj: object, parentBlock?: CalldataBlock): SetCalldataBlock {
        // Create a new calldata block for this set.
        const parentName = _.isUndefined(parentBlock) ? '' : parentBlock.getName();
        const block = new SetCalldataBlock(this.getDataItem().name, this.getSignature(), parentName);
        // Create blocks for members of set.
        const memberCalldataBlocks: CalldataBlock[] = [];
        const childMap = _.cloneDeep(this._memberIndexByName);
        _.forOwn(obj, (value: any, key: string) => {
            if (!(key in childMap)) {
                throw new Error(
                    `Could not assign tuple to object: unrecognized key '${key}' in object ${this.getDataItem().name}`,
                );
            }
            const memberBlock = this._members[this._memberIndexByName[key]].generateCalldataBlock(value, block);
            memberCalldataBlocks.push(memberBlock);
            delete childMap[key];
        });
        // Sanity check that all members have been included.
        if (Object.keys(childMap).length !== 0) {
            throw new Error(`Could not assign tuple to object: missing keys ${Object.keys(childMap)}`);
        }
        // Associate member blocks with Set block.
        block.setMembers(memberCalldataBlocks);
        return block;
    }

    protected _computeSignatureOfMembers(): string {
        // Compute signature of members
        let signature = `(`;
        _.each(this._members, (member: DataType, i: number) => {
            signature += member.getSignature();
            if (i < this._members.length - 1) {
                signature += ',';
            }
        });
        signature += ')';
        return signature;
    }

    private _createMembersWithKeys(dataItem: DataItem): [DataType[], MemberIndexByName] {
        // Sanity check
        if (_.isUndefined(dataItem.components)) {
            throw new Error(
                `Tried to create a set using key/value pairs, but no components were defined by the input DataItem '${
                    dataItem.name
                }'.`,
            );
        }
        // Create one member for each component of `dataItem`
        const members: DataType[] = [];
        const memberIndexByName: MemberIndexByName = {};
        _.each(dataItem.components, (memberItem: DataItem) => {
            const childDataItem: DataItem = {
                type: memberItem.type,
                name: `${dataItem.name}.${memberItem.name}`,
            };
            const components = memberItem.components;
            if (!_.isUndefined(components)) {
                childDataItem.components = components;
            }
            const child = this.getFactory().create(childDataItem, this);
            memberIndexByName[memberItem.name] = members.length;
            members.push(child);
        });
        return [members, memberIndexByName];
    }

    private _createMembersWithLength(dataItem: DataItem, length: number): [DataType[], MemberIndexByName] {
        // Create `length` members, deriving the type from `dataItem`
        const members: DataType[] = [];
        const memberIndexByName: MemberIndexByName = {};
        const range = _.range(length);
        _.each(range, (idx: number) => {
            const memberDataItem: DataItem = {
                type: _.isUndefined(this._arrayElementType) ? '' : this._arrayElementType,
                name: `${dataItem.name}[${idx.toString(constants.DEC_BASE)}]`,
            };
            const components = dataItem.components;
            if (!_.isUndefined(components)) {
                memberDataItem.components = components;
            }
            const memberType = this.getFactory().create(memberDataItem, this);
            memberIndexByName[idx.toString(constants.DEC_BASE)] = members.length;
            members.push(memberType);
        });
        return [members, memberIndexByName];
    }
}