PHP WebShell
Текущая директория: /opt/BitGoJS/modules/sdk-coin-trx/src/lib
Просмотр файла: contractCallBuilder.ts
import { createHash } from 'crypto';
import { BaseCoin as CoinConfig, TronNetwork } from '@bitgo/statics';
import BigNumber from 'bignumber.js';
import { protocol } from '../../resources/protobuf/tron';
import {
BaseKey,
BuildTransactionError,
ExtendTransactionError,
InvalidParameterValueError,
SigningError,
TransactionType,
} from '@bitgo/sdk-core';
import { TransactionBuilder } from './transactionBuilder';
import { Address } from './address';
import { Transaction } from './transaction';
import { Block, Fee, TransactionReceipt, TriggerSmartContract } from './iface';
import {
decodeTransaction,
getBase58AddressFromHex,
getByteArrayFromHexAddress,
getHexAddressFromBase58Address,
isValidHex,
} from './utils';
import ContractType = protocol.Transaction.Contract.ContractType;
const DEFAULT_EXPIRATION = 3600000; // one hour
const MAX_DURATION = 31536000000; // one year
export const MAX_FEE = 5000000000; // 5e9 = 5000 TRX acording https://developers.tron.network/docs/setting-a-fee-limit-on-deployexecution
export class ContractCallBuilder extends TransactionBuilder {
protected _signingKeys: BaseKey[];
private _toContractAddress: string;
private _data: string;
private _ownerAddress: string;
private _refBlockBytes: string;
private _refBlockHash: string;
private _expiration: number;
private _timestamp: number;
private _fee: Fee;
constructor(_coinConfig: Readonly<CoinConfig>) {
super(_coinConfig);
this._signingKeys = [];
this.transaction = new Transaction(_coinConfig);
}
/** @inheritdoc */
protected async buildImplementation(): Promise<Transaction> {
this.createTransaction();
/** @inheritdoc */
// This method must be extended on child classes
if (this._signingKeys.length > 0) {
this.applySignatures();
}
if (!this.transaction.id) {
throw new BuildTransactionError('A valid transaction must have an id');
}
return Promise.resolve(this.transaction);
}
/** @inheritdoc */
protected signImplementation(key: BaseKey): Transaction {
if (this._signingKeys.some((signingKey) => signingKey.key === key.key)) {
throw new SigningError('Duplicated key');
}
this._signingKeys.push(key);
// We keep this return for compatibility but is not meant to be use
return this.transaction;
}
/**
* Initialize the transaction builder fields using the transaction data
*
* @param {any} rawTransaction the transaction data in a string or JSON format
* @returns {ContractCallBuilder} the builder with the transaction data set
*/
initBuilder(rawTransaction: TransactionReceipt | string): this {
this.validateRawTransaction(rawTransaction);
const tx = this.fromImplementation(rawTransaction);
this.transaction = tx;
this._signingKeys = [];
const rawData = tx.toJson().raw_data;
this._refBlockBytes = rawData.ref_block_bytes;
this._refBlockHash = rawData.ref_block_hash;
this._expiration = rawData.expiration;
this._timestamp = rawData.timestamp;
this._fee = { feeLimit: rawData.fee_limit!.toString() };
this.transaction.setTransactionType(TransactionType.ContractCall);
const contractCall = rawData.contract[0] as TriggerSmartContract;
this.initContractCall(contractCall);
return this;
}
/**
* Initialize the contract call specific data
*
* @param {TriggerSmartContract} contractCall object with transfer data
*/
protected initContractCall(contractCall: TriggerSmartContract): void {
const { data, owner_address, contract_address } = contractCall.parameter.value;
if (data) {
this.data(data);
}
if (contract_address) {
this.to({ address: getBase58AddressFromHex(contract_address) });
}
if (owner_address) {
this.source({ address: getBase58AddressFromHex(owner_address) });
}
}
// region Contract Call fields
/**
* Set the source address,
*
* @param {Address} address source account
* @returns {ContractCallBuilder} the builder with the new parameter set
*/
source(address: Address): this {
this.validateAddress(address);
this._ownerAddress = getHexAddressFromBase58Address(address.address);
return this;
}
/**
* Set the address of the contract to be called,
*
* @param {Address} contractAddress the contract address
* @returns {ContractCallBuilder} the builder with the new parameter set
*/
to(contractAddress: Address): this {
this.validateAddress(contractAddress);
this._toContractAddress = getHexAddressFromBase58Address(contractAddress.address);
return this;
}
/**
* Set the data with the method call and parameters
*
* @param {string} data data encoded on hexa
* @returns {ContractCallBuilder} the builder with the new parameter set
*/
data(data: string): this {
if (!isValidHex(data)) {
throw new InvalidParameterValueError(data + ' is not a valid hex string.');
}
this._data = data;
return this;
}
/**
* Set the block values,
*
* @param {Block} block the object containing number and hash of the block
* @returns {ContractCallBuilder} the builder with the new parameter set
*/
block(block: Block): this {
const blockBytes = Buffer.alloc(8);
blockBytes.writeInt32BE(block.number, 4);
this._refBlockBytes = blockBytes.slice(6, 8).toString('hex');
this._refBlockHash = Buffer.from(block.hash, 'hex').slice(8, 16).toString('hex');
return this;
}
/**
* Set the expiration time for the transaction, set also timestamp if it was not set previously
*
* @param {number} time the expiration time in milliseconds
* @returns {ContractCallBuilder} the builder with the new parameter set
*/
expiration(time: number): this {
if (this.transaction.id) {
throw new ExtendTransactionError('Expiration is already set, it can only be extended');
}
this._timestamp = this._timestamp || Date.now();
this.validateExpirationTime(time);
this._expiration = time;
return this;
}
/** @inheritdoc */
extendValidTo(extensionMs: number): void {
if (this.transaction.signature && this.transaction.signature.length > 0) {
throw new ExtendTransactionError('Cannot extend a signed transaction');
}
if (extensionMs <= 0) {
throw new Error('Value cannot be below zero');
}
if (extensionMs > MAX_DURATION) {
throw new ExtendTransactionError('The expiration cannot be extended more than one year');
}
if (this._expiration) {
this._expiration = this._expiration + extensionMs;
} else {
throw new Error('There is not expiration to extend');
}
}
/**
* Set the timestamp for the transaction
*
* @param {number} time the timestamp in milliseconds
* @returns {ContractCallBuilder} the builder with the new parameter set
*/
timestamp(time: number): this {
this._timestamp = time;
return this;
}
/**
* Set the fee limit for the transaction
*
* @param {Fee} fee the fee limit for the transaction
* @returns {ContractCallBuilder} the builder with the new parameter set
*/
fee(fee: Fee): this {
const feeLimit = new BigNumber(fee.feeLimit);
const tronNetwork = this._coinConfig.network as TronNetwork;
if (feeLimit.isNaN() || feeLimit.isLessThan(0) || feeLimit.isGreaterThan(tronNetwork.maxFeeLimit)) {
throw new InvalidParameterValueError('Invalid fee limit value');
}
this._fee = fee;
return this;
}
// endregion
private createTransaction(): void {
const rawDataHex = this.getRawDataHex();
const rawData = decodeTransaction(rawDataHex);
const contract = rawData.contract[0] as TriggerSmartContract;
const contractParameter = contract.parameter;
contractParameter.value.contract_address = this._toContractAddress.toLocaleLowerCase();
contractParameter.value.owner_address = this._ownerAddress.toLocaleLowerCase();
contractParameter.value.data = this._data.toLocaleLowerCase();
contractParameter.type_url = 'type.googleapis.com/protocol.TriggerSmartContract';
contract.type = 'TriggerSmartContract';
const hexBuffer = Buffer.from(rawDataHex, 'hex');
const id = createHash('sha256').update(hexBuffer).digest('hex');
const txRecip: TransactionReceipt = {
raw_data: rawData,
raw_data_hex: rawDataHex,
txID: id,
signature: this.transaction.signature,
};
this.transaction = new Transaction(this._coinConfig, txRecip);
}
private getRawDataHex(): string {
const rawContract = {
ownerAddress: getByteArrayFromHexAddress(this._ownerAddress),
contractAddress: getByteArrayFromHexAddress(this._toContractAddress),
data: getByteArrayFromHexAddress(this._data),
};
const contractCall = protocol.TriggerSmartContract.fromObject(rawContract);
const contractBytes = protocol.TriggerSmartContract.encode(contractCall).finish();
const txContract = {
type: ContractType.TriggerSmartContract,
parameter: {
value: contractBytes,
type_url: 'type.googleapis.com/protocol.TriggerSmartContract',
},
};
const raw = {
refBlockBytes: Buffer.from(this._refBlockBytes, 'hex'),
refBlockHash: Buffer.from(this._refBlockHash, 'hex'),
expiration: this._expiration || Date.now() + DEFAULT_EXPIRATION,
timestamp: this._timestamp || Date.now(),
contract: [txContract],
feeLimit: parseInt(this._fee.feeLimit, 10),
};
const rawTx = protocol.Transaction.raw.create(raw);
return Buffer.from(protocol.Transaction.raw.encode(rawTx).finish()).toString('hex');
}
private applySignatures(): void {
if (!this.transaction.inputs) {
throw new SigningError('Transaction has no inputs');
}
this._signingKeys.forEach((key) => this.applySignature(key));
}
/** @inheritdoc */
// Specifically, checks hex underlying transaction hashes to correct transaction ID.
validateTransaction(transaction: Transaction): void {
this.validateMandatoryFields();
}
/** @inheritdoc */
validateMandatoryFields() {
if (!this._data) {
throw new BuildTransactionError('Missing parameter: data');
}
if (!this._ownerAddress) {
throw new BuildTransactionError('Missing parameter: source');
}
if (!this._toContractAddress) {
throw new BuildTransactionError('Missing parameter: contract address');
}
if (!this._refBlockBytes || !this._refBlockHash) {
throw new BuildTransactionError('Missing block reference information');
}
if (!this._fee) {
throw new BuildTransactionError('Missing fee');
}
}
validateExpirationTime(value: number): void {
if (value < this._timestamp) {
throw new InvalidParameterValueError('Expiration must be greater than timestamp');
}
if (value < Date.now()) {
throw new InvalidParameterValueError('Expiration must be greater than current time');
}
if (value - this._timestamp > MAX_DURATION) {
throw new InvalidParameterValueError('Expiration must not be greater than one year');
}
}
}
Выполнить команду
Для локальной разработки. Не используйте в интернете!