UNPKG

vasku

Version:

TVM-Solidity contract development framework

361 lines (360 loc) 12.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Contract = exports.AccountType = void 0; const global_1 = require("../global"); const constants_1 = require("./constants"); const payload_1 = require("./payload"); const generateRandomKeyPair_1 = require("../utils/generateRandomKeyPair"); var AccountType; (function (AccountType) { AccountType["notFound"] = "-1"; AccountType["unInit"] = "0"; AccountType["active"] = "1"; AccountType["frozen"] = "2"; AccountType["nonExist"] = "3"; })(AccountType || (exports.AccountType = AccountType = {})); class Contract { abi; initial; tvc; client; giver; _address; _keys; _lastTransactionLogicTime; constructor(config, options = {}) { this._address = config.address; this._keys = config.keys; this._lastTransactionLogicTime = '0'; this.abi = { type: 'Contract', value: config.abi ?? {} }; this.initial = config.initial ?? {}; this.tvc = config.tvc; this.client = options.client ?? global_1.Global.client; this.giver = options.giver ?? global_1.Global.giver; } /** * Calculates the address only once. Next time returns the already calculated address * You can use this if you want to know the address of the contract before deploying * @example * const contract = new Contract(...) * const address = await contract.address() * @return * '0:97b53be2604579e89bd0077a5456456857792eb2ff09849d14321fc2c167f29e' */ async address() { if (this._address !== undefined) return this._address; if (this.client === undefined) throw constants_1.error.noClient; if (this.tvc === undefined) throw new Error('Contract tvc is undefined'); this._address = (await this.client.abi.encode_message({ abi: this.abi, signer: { type: 'Keys', keys: await this.keys() }, deploy_set: { tvc: this.tvc, initial_data: this.initial } })).address; return this._address; } /** * Generates a random key pair if no keys are given. Next time returns the already generated key pair * @example * const contract = new Contract(...) * const address = await contract.keyPair() * @return * '0:97b53be2604579e89bd0077a5456456857792eb2ff09849d14321fc2c167f29e' */ async keys() { if (this._keys !== undefined) return this._keys; if (this.client === undefined) throw constants_1.error.noClient; if (this.tvc === undefined) throw new Error('Contract tvc is undefined'); this._keys = await (0, generateRandomKeyPair_1.generateRandomKeyPair)(this.client); return this._keys; } /** * Return contract balance * @example * const contract = new Contract(...) * const address = await contract.balance() * @return * 10000000000n */ async balance() { if (this.client === undefined) throw constants_1.error.noClient; const result = (await this.client.net.query_collection({ collection: 'accounts', filter: { id: { eq: await this.address() }, last_trans_lt: { ge: this._lastTransactionLogicTime } }, result: 'balance' })).result; return (result !== undefined && result.length > 0) ? BigInt(result[0].balance) : BigInt(0); } /** * Return contract account type * @example * const contract = new Contract(...) * const address = await contract.accountType() * @return * 1 */ async accountType() { if (this.client === undefined) throw constants_1.error.noClient; const result = (await this.client.net.query_collection({ collection: 'accounts', filter: { id: { eq: await this.address() }, last_trans_lt: { ge: this._lastTransactionLogicTime } }, result: 'acc_type' })).result; return (result !== undefined && result.length > 0) ? result[0].acc_type.toString() : AccountType.nonExist; } /** * Run contract locally * @param method Method name * @param input * @example * const contract = new Contract(...) * const address = await contract.runMethod('getHistory', { offset: 1 }) * @return { DecodedMessageBody } */ async runMethod(method, input = {}) { if (this.client === undefined) throw constants_1.error.noClient; ////////////////////// // Read Bag Of Cell // ////////////////////// const bagOfCell = (await this.client.net.query_collection({ collection: 'accounts', filter: { id: { eq: await this.address() } }, result: 'boc' })).result[0].boc; ///////////////////////////// // Encode external message // ///////////////////////////// const encodedMessage = await this.client.abi.encode_message({ abi: this.abi, signer: { type: 'None' }, call_set: { function_name: method, input }, address: this._address }); ///////////////////////////////////////// // Execute contract on virtual machine // ///////////////////////////////////////// const outputMessage = (await this.client.tvm.run_tvm({ message: encodedMessage.message, account: bagOfCell })).out_messages[0]; /////////////////////////// // Decode output message // /////////////////////////// return await this.client.abi.decode_message({ abi: this.abi, message: outputMessage }); } /** * External call * @param method Method name * @param input * @param [keys] Use it if you want to call contact with keys. `this.keys` used by default. * @example * const contract = new Contract(...) * const address = await contract.runMethod('getHistory', { offset: 1 }) */ async callMethod(method, input = {}, keys) { if (this.client === undefined) throw constants_1.error.noClient; /////////////////////////////// // Generate external message // /////////////////////////////// const resultOfProcessMessage = await this.client.processing.process_message({ message_encode_params: { abi: this.abi, signer: { type: 'Keys', keys: keys ?? await this.keys() }, address: await this.address(), call_set: { function_name: method, input } }, send_events: false }); //////////////////// // Decode message // //////////////////// this._lastTransactionLogicTime = resultOfProcessMessage.transaction.lt.toString() ?? this._lastTransactionLogicTime; return { out: resultOfProcessMessage.decoded?.output ?? {}, result: resultOfProcessMessage }; } /** * Return last transaction logic time * @example * const contract = new Contract(...) * contract.lastTransactionLogicTime * @return * '0' */ get lastTransactionLogicTime() { return this._lastTransactionLogicTime; } /** * Deploy * @param value Deployment in nano coins * @param input constructor data * @param useGiver Send coins from giver before deployment if true * @param timeout How long to wait coins from giver on contract in milliseconds */ async _deploy(value, input = {}, useGiver = true, timeout = 60000) { //////////////////////////// // Check all requirements // //////////////////////////// const accountType = await this.accountType(); if (accountType === AccountType.active) throw constants_1.error.contractAlreadyDeployed; if (this.client === undefined) throw constants_1.error.noClient; if (this.tvc === undefined) throw constants_1.error.noTvc; if (useGiver && this.giver === undefined) throw constants_1.error.noGiver; /////////////// // Use giver // /////////////// const to = await this.address(); if (useGiver && this.giver !== undefined) await this.giver.send({ to, value }); ///////////////////////////// // Waiting for balance > 0 // ///////////////////////////// await this.client.net.wait_for_collection({ collection: 'accounts', filter: { id: { eq: await this.address() }, data: { eq: null }, code: { eq: null }, balance: { gt: '0' } }, result: 'last_trans_lt', timeout }); //////////// // Deploy // //////////// const resultOfProcessMessage = await this.client.processing.process_message({ message_encode_params: { abi: this.abi, signer: { type: 'Keys', keys: await this.keys() }, deploy_set: { tvc: this.tvc, initial_data: this.initial }, call_set: { function_name: 'constructor', input } }, send_events: false }); this._lastTransactionLogicTime = resultOfProcessMessage.transaction.lt ?? this._lastTransactionLogicTime; return resultOfProcessMessage; } /** * Use this if you want to wait for a transaction from one contract to another * @example * const sender = new SenderContract(...) * const receiver = new ReceiverContract(...) * const to = await receiver.address() * await sender.call.send({to, value: 1_000_000_000}) * const waitingResult = await receiver.wait(5000) * @param timeout Time in milliseconds * @return * true */ async wait(timeout = 60000) { if (this.client === undefined) throw constants_1.error.noClient; try { const queryCollectionResult = await this.client.net.wait_for_collection({ collection: 'accounts', filter: { id: { eq: await this.address() }, last_trans_lt: { gt: this._lastTransactionLogicTime } }, result: 'last_trans_lt', timeout }); const result = queryCollectionResult.result; this._lastTransactionLogicTime = result.last_trans_lt ?? '0'; return true; } catch (e) { } return false; } /** * Create payload * @example * const contract = new Contract(...) * const payload = await contract.createPayload('bet', { luckyNumber: 5}) * @param method Method name * @param input */ async createPayload(method, input = {}) { if (this.client === undefined) throw constants_1.error.noClient; return await (0, payload_1.createPayload)(this.abi.value, method, input, this.client); } } exports.Contract = Contract;