PHP WebShell

Текущая директория: /opt/BitGoJS/modules/sdk-coin-celo/src/lib

Просмотр файла: stakingBuilder.ts

import * as ethUtil from 'ethereumjs-util';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import {
  isValidAmount,
  isValidEthAddress,
  getRawDecoded,
  getBufferedByteCode,
  hexStringToNumber,
} from '@bitgo/sdk-coin-eth';
import {
  ActivateMethodId,
  BuildTransactionError,
  getOperationConfig,
  InvalidParameterValueError,
  InvalidTransactionError,
  StakingOperationTypes,
  UnlockMethodId,
  UnvoteMethodId,
  VoteMethodId,
  WithdrawMethodId,
} from '@bitgo/sdk-core';
import { StakingCall } from './stakingCall';

export class StakingBuilder {
  private readonly DEFAULT_ADDRESS = '0x0000000000000000000000000000000000000000';
  private _amount: string;
  private _validatorGroup: string;
  private _lesser = this.DEFAULT_ADDRESS;
  private _greater = this.DEFAULT_ADDRESS;
  private _index: number;
  private _type: StakingOperationTypes;
  private _coinConfig: Readonly<CoinConfig>;

  constructor(coinConfig: Readonly<CoinConfig>, serializedData?: string) {
    this._coinConfig = coinConfig;
    if (serializedData) {
      this.decodeStakingData(serializedData);
    }
  }

  // region Staking properties

  type(type: StakingOperationTypes): this {
    this._type = type;
    return this;
  }

  amount(value: string): this {
    if (!isValidAmount(value)) {
      throw new InvalidParameterValueError('Invalid value for stake transaction');
    }
    this._amount = value;
    return this;
  }

  group(validatorGroup: string): this {
    if (!isValidEthAddress(validatorGroup)) {
      throw new InvalidParameterValueError('Invalid validator group address');
    }
    this._validatorGroup = validatorGroup;
    return this;
  }

  lesser(lesser: string): this {
    if (!isValidEthAddress(lesser)) {
      throw new InvalidParameterValueError('Invalid address for lesser');
    }
    this._lesser = lesser;
    return this;
  }

  greater(greater: string): this {
    if (!isValidEthAddress(greater)) {
      throw new InvalidParameterValueError('Invalid address for greater');
    }
    this._greater = greater;
    return this;
  }

  index(index: number): this {
    if (index < 0) {
      throw new InvalidParameterValueError('Invalid index for staking transaction');
    }
    this._index = index;
    return this;
  }

  // endregion

  // region Staking building

  build(): StakingCall {
    this.validateMandatoryFields();
    switch (this._type) {
      case StakingOperationTypes.LOCK:
        this.validateAmount();
        return this.buildLockStaking();
      case StakingOperationTypes.VOTE:
        this.validateElectionFields();
        return this.buildVoteStaking();
      case StakingOperationTypes.ACTIVATE:
        this.validateGroup();
        return this.buildActivateStaking();
      case StakingOperationTypes.UNVOTE:
        this.validateUnvoteFields();
        return this.buildUnvoteStaking();
      case StakingOperationTypes.UNLOCK:
        this.validateAmount();
        return this.buildUnlockStaking();
      case StakingOperationTypes.WITHDRAW:
        this.validateIndex();
        return this.buildWithdrawStaking();
      default:
        throw new InvalidTransactionError('Invalid staking operation: ' + this._type);
    }
  }

  /**
   * Builds a lock gold operation sending the amount on the transaction value field
   *
   * @returns {StakingCall} a lock gold operation using the LockedGold contract
   */
  private buildLockStaking(): StakingCall {
    const operation = getOperationConfig(this._type, this._coinConfig.network.type);
    return new StakingCall(this._amount, operation.contractAddress, operation.methodId, operation.types, []);
  }

  /**
   * Builds an unlock gold operation sending the amount encoded on the data field
   *
   * params
   * amount: amount of locked gold to be unlocked
   *
   * @returns {StakingCall} an unlock gold operation using the LockedGold contract
   */
  private buildUnlockStaking(): StakingCall {
    const operation = getOperationConfig(this._type, this._coinConfig.network.type);
    const params = [this._amount];
    return new StakingCall('0', operation.contractAddress, operation.methodId, operation.types, params);
  }

  /**
   * Builds a vote operation that uses locked gold to add pending votes for a validator group.
   *
   * params
   * validatorGroup: group to vote for
   * amount: amount of votes (locked gold) for the group
   * lesser: validator group that has less votes than the validatorGroup
   * greater: validator group that has more vots than the validatorGroup
   *
   * @returns {StakingCall} an vote operation using the Election contract
   */
  private buildVoteStaking(): StakingCall {
    const operation = getOperationConfig(this._type, this._coinConfig.network.type);
    const params = [this._validatorGroup, this._amount, this._lesser, this._greater];
    return new StakingCall('0', operation.contractAddress, operation.methodId, operation.types, params);
  }

  /**
   * Builds an unvote operation to revoke active votes for a validator group.
   *
   * params
   * validatorGroup: group whose votes will be revoked
   * amount: amount of votes (locked gold) that will be revoked
   * lesser: validator group that has less votes than the validatorGroup
   * greater: validator group that has more vots than the validatorGroup
   * index: index of the validatorGroup on the list of groups the address has voted for
   *
   * @returns {StakingCall} an vote operation using the Election contract
   */
  private buildUnvoteStaking(): StakingCall {
    const operation = getOperationConfig(this._type, this._coinConfig.network.type);
    const params = [this._validatorGroup, this._amount, this._lesser, this._greater, this._index.toString()];
    return new StakingCall('0', operation.contractAddress, operation.methodId, operation.types, params);
  }

  /**
   * Builds an activate vote operation to change all the votes casted for a validator
   * from 'pending' to 'active'
   *
   * params
   * validatorGroup: group whose votes will be activated
   *
   * @returns {StakingCall} an activate votes operation
   */
  private buildActivateStaking(): StakingCall {
    const operation = getOperationConfig(this._type, this._coinConfig.network.type);
    const params = [this._validatorGroup];
    return new StakingCall('0', operation.contractAddress, operation.methodId, operation.types, params);
  }

  /**
   * Builds a withdraw operation for locked gold that has been unlocked
   * after the unlocking period has passed.
   *
   * params
   * index: index of the unlock operation whose unlocking period has passed.
   *
   * @returns {StakingCall} an activate votes operation
   */
  private buildWithdrawStaking(): StakingCall {
    const operation = getOperationConfig(this._type, this._coinConfig.network.type);
    const params = [this._index.toString()];
    return new StakingCall('0', operation.contractAddress, operation.methodId, operation.types, params);
  }

  // endregion

  // region Validation methods

  private validateMandatoryFields(): void {
    if (!(this._type !== undefined && this._coinConfig)) {
      throw new BuildTransactionError('Missing staking mandatory fields. Type and coin are required');
    }
  }

  private validateElectionFields(): void {
    this.validateGroup();
    this.validateAmount();
    if (this._lesser === this._greater) {
      throw new BuildTransactionError('Greater and lesser values should not be the same');
    }
  }

  private validateIndex(): void {
    if (this._index === undefined) {
      throw new BuildTransactionError('Missing index for staking transaction');
    }
  }

  private validateAmount(): void {
    if (this._amount === undefined) {
      throw new BuildTransactionError('Missing amount for staking transaction');
    }
  }

  private validateUnvoteFields(): void {
    this.validateElectionFields();
    this.validateIndex();
  }

  private validateGroup(): void {
    if (!this._validatorGroup) {
      throw new BuildTransactionError('Missing validator group for staking transaction');
    }
  }

  // endregion

  // region Deserialization methods
  private decodeStakingData(data: string): void {
    this.classifyStakingType(data);

    const operation = getOperationConfig(this._type, this._coinConfig.network.type);
    const decoded = getRawDecoded(operation.types, getBufferedByteCode(operation.methodId, data));
    switch (this._type) {
      case StakingOperationTypes.VOTE:
        this.validateDecodedDataLength(decoded.length, 4, data);
        const [groupToVote, amount, lesser, greater] = decoded;
        this._amount = ethUtil.bufferToHex(amount as Buffer);
        this._validatorGroup = ethUtil.addHexPrefix(groupToVote as string);
        this._lesser = ethUtil.addHexPrefix(lesser as string);
        this._greater = ethUtil.addHexPrefix(greater as string);
        break;
      case StakingOperationTypes.UNVOTE:
        this.validateDecodedDataLength(decoded.length, 5, data);
        const [groupToUnvote, amountUnvote, lesserUnvote, greaterUnvote, indexUnvote] = decoded;
        this._validatorGroup = ethUtil.addHexPrefix(groupToUnvote as string);
        this._amount = ethUtil.bufferToHex(amountUnvote as Buffer);
        this._lesser = ethUtil.addHexPrefix(lesserUnvote as string);
        this._greater = ethUtil.addHexPrefix(greaterUnvote as string);
        this._index = hexStringToNumber(ethUtil.bufferToHex(indexUnvote as Buffer));
        break;
      case StakingOperationTypes.ACTIVATE:
        this.validateDecodedDataLength(decoded.length, 1, data);
        const [groupToActivate] = decoded;
        this._validatorGroup = ethUtil.addHexPrefix(groupToActivate as string);
        break;
      case StakingOperationTypes.UNLOCK:
        if (decoded.length !== 1) {
          throw new BuildTransactionError(`Invalid unlock decoded data: ${data}`);
        }
        const [decodedAmount] = decoded;
        this._amount = ethUtil.bufferToHex(decodedAmount as Buffer);
        break;
      case StakingOperationTypes.WITHDRAW:
        this.validateDecodedDataLength(decoded.length, 1, data);
        const [index] = decoded;
        this._index = hexStringToNumber(ethUtil.bufferToHex(index as Buffer));
        break;
      default:
        throw new BuildTransactionError(`Invalid staking data: ${this._type}`);
    }
  }

  private validateDecodedDataLength(actual: number, expected: number, data: string): void {
    if (actual !== expected) {
      throw new BuildTransactionError(`Invalid staking decoded data: ${data}`);
    }
  }

  private classifyStakingType(data: string): void {
    if (data.startsWith(VoteMethodId)) {
      this._type = StakingOperationTypes.VOTE;
    } else if (data.startsWith(UnvoteMethodId)) {
      this._type = StakingOperationTypes.UNVOTE;
    } else if (data.startsWith(ActivateMethodId)) {
      this._type = StakingOperationTypes.ACTIVATE;
    } else if (data.startsWith(UnlockMethodId)) {
      this._type = StakingOperationTypes.UNLOCK;
    } else if (data.startsWith(WithdrawMethodId)) {
      this._type = StakingOperationTypes.WITHDRAW;
    } else {
      throw new BuildTransactionError(`Invalid staking bytecode: ${data}`);
    }
  }

  // endregion
}

Выполнить команду


Для локальной разработки. Не используйте в интернете!