vasku
Version:
TVM-Solidity contract development framework
361 lines (360 loc) • 12.3 kB
JavaScript
"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;