mirror of
https://github.com/status-im/web3.js.git
synced 2025-02-23 19:48:13 +00:00
* added sub packages * added lerna monopackage management * check for package is instance * added method, subscription and utils package * moved almost all packages * moved all packages, no umbrella package yet * added extend to packages * made contract pass * made batch tests pass * expose providers * fixed test async * fixed test errors * fixed test event encode decode * fixed test formatter tests * fixed method tests * fixed method utils * fixed some eth methods * fixed some eth methods 2 * bumped version 0.18.3 to republish meteor package * fixed get* tests * fixed subscribe tests * added newBlockHeaders subscription * remove unpublished package from package.json * added sendTransaction test * fixed call test * moved files to done * changed extend * added iban tests * Fixed ALL tests * Fixed lint tests * Fixed contract tests * added method tests * added more method tests to test promiEvents extensively * added confirmation event * improved method confirmation checking
677 lines
24 KiB
JavaScript
677 lines
24 KiB
JavaScript
var chai = require('chai');
|
|
var assert = chai.assert;
|
|
var formatters = require('../packages/web3-core-helpers/src/formatters.js');
|
|
var FakeHttpProvider = require('./helpers/FakeHttpProvider');
|
|
var Eth = require('../packages/web3-eth');
|
|
var Method = require('../packages/web3-core-method');
|
|
|
|
var address = '0x1234567890123456789012345678901234567891';
|
|
|
|
|
|
describe('lib/web3/method', function () {
|
|
describe('buildCall', function () {
|
|
it('should return a promise and resolve it', function (done) {
|
|
var provider = new FakeHttpProvider();
|
|
var eth = new Eth(provider);
|
|
var method = new Method({
|
|
name: 'call',
|
|
call: 'eth_call',
|
|
params: 2,
|
|
inputFormatter: [formatters.inputCallFormatter, formatters.inputDefaultBlockNumberFormatter]
|
|
});
|
|
method.setRequestManager(eth._requestManager);
|
|
|
|
// generate send function
|
|
var send = method.buildCall();
|
|
|
|
// add results
|
|
provider.injectValidation(function (payload) {
|
|
assert.equal(payload.method, 'eth_call');
|
|
assert.deepEqual(payload.params, [{
|
|
from: '0x11f4d0a3c12e86b4b5f39b213f7e19d048276dae',
|
|
to: '0x11f4d0a3c12e86b4b5f39b213f7e19d048276dae',
|
|
data: '0xa123456'
|
|
},"latest"]);
|
|
});
|
|
provider.injectResult('0x1234567453543456321456321'); // tx hash
|
|
|
|
send({
|
|
from: '0x11f4d0A3c12e86B4b5F39B213F7E19D048276DAe',
|
|
to: '0x11f4d0A3c12e86B4b5F39B213F7E19D048276DAe',
|
|
data: '0xa123456'
|
|
}).then(function (result) {
|
|
|
|
assert.deepEqual(result, '0x1234567453543456321456321');
|
|
|
|
done();
|
|
});
|
|
|
|
});
|
|
it('should return a promise and fail it', function (done) {
|
|
var provider = new FakeHttpProvider();
|
|
var eth = new Eth(provider);
|
|
var method = new Method({
|
|
name: 'call',
|
|
call: 'eth_call',
|
|
params: 2,
|
|
inputFormatter: [formatters.inputCallFormatter, formatters.inputDefaultBlockNumberFormatter]
|
|
});
|
|
method.setRequestManager(eth._requestManager);
|
|
|
|
// generate send function
|
|
var send = method.buildCall();
|
|
|
|
// add results
|
|
provider.injectValidation(function (payload) {
|
|
assert.equal(payload.method, 'eth_call');
|
|
assert.deepEqual(payload.params, [{
|
|
from: '0x11f4d0a3c12e86b4b5f39b213f7e19d048276dae',
|
|
to: '0x11f4d0a3c12e86b4b5f39b213f7e19d048276dae',
|
|
data: '0xa123456'
|
|
},"latest"]);
|
|
});
|
|
provider.injectError({
|
|
message: 'Wrong!',
|
|
code: 1234
|
|
});
|
|
|
|
|
|
send({
|
|
from: '0x11f4d0A3c12e86B4b5F39B213F7E19D048276DAe',
|
|
to: '0x11f4d0A3c12e86B4b5F39B213F7E19D048276DAe',
|
|
data: '0xa123456'
|
|
})
|
|
.catch(function (error) {
|
|
assert.deepEqual(error, {
|
|
message: 'Wrong!',
|
|
code: 1234
|
|
});
|
|
|
|
done();
|
|
});
|
|
|
|
});
|
|
|
|
var succeedOnReceipt = function () {
|
|
var provider = new FakeHttpProvider();
|
|
var eth = new Eth(provider);
|
|
var method = new Method({
|
|
name: 'sendTransaction',
|
|
call: 'eth_sendTransaction',
|
|
params: 1,
|
|
inputFormatter: [formatters.inputTransactionFormatter]
|
|
});
|
|
method.setRequestManager(eth._requestManager, eth);
|
|
|
|
// generate send function
|
|
var send = method.buildCall();
|
|
|
|
// add results
|
|
provider.injectValidation(function (payload) {
|
|
assert.equal(payload.method, 'eth_sendTransaction');
|
|
assert.deepEqual(payload.params, [{
|
|
from: '0x11f4d0a3c12e86b4b5f39b213f7e19d048276dae',
|
|
to: '0x11f4d0a3c12e86b4b5f39b213f7e19d048276dae',
|
|
value: '0xa'
|
|
}]);
|
|
});
|
|
provider.injectResult('0x1234567453543456321456321'); // tx hash
|
|
|
|
|
|
provider.injectValidation(function (payload) {
|
|
assert.equal(payload.method, 'eth_subscribe');
|
|
assert.deepEqual(payload.params, ['newHeads']);
|
|
});
|
|
provider.injectResult('0x1234567'); // subscription id
|
|
|
|
// fake newBlock
|
|
provider.injectNotification({
|
|
method: 'eth_subscription',
|
|
params: {
|
|
subscription: '0x1234567',
|
|
result: {
|
|
blockNumber: '0x10'
|
|
}
|
|
}
|
|
});
|
|
|
|
// receipt
|
|
provider.injectValidation(function (payload) {
|
|
assert.equal(payload.method, 'eth_getTransactionReceipt');
|
|
assert.deepEqual(payload.params, ['0x1234567453543456321456321']);
|
|
});
|
|
provider.injectResult({
|
|
contractAddress: address,
|
|
cumulativeGasUsed: '0xa',
|
|
transactionIndex: '0x3',
|
|
blockNumber: '0xa'
|
|
});
|
|
|
|
provider.injectValidation(function (payload) {
|
|
assert.equal(payload.method, 'eth_unsubscribe');
|
|
assert.deepEqual(payload.params, ['0x1234567']);
|
|
});
|
|
provider.injectResult(true); // unsubscribe result
|
|
|
|
return send;
|
|
};
|
|
|
|
it('should use promise "then" when subscribing and checking for receipt if "sendTransaction"', function (done) {
|
|
|
|
var send = succeedOnReceipt();
|
|
|
|
send({
|
|
from: '0x11f4d0A3c12e86B4b5F39B213F7E19D048276DAe',
|
|
to: '0x11f4d0A3c12e86B4b5F39B213F7E19D048276DAe',
|
|
value: '0xa'
|
|
}).then(function (result) {
|
|
|
|
|
|
assert.deepEqual(result, {
|
|
contractAddress: address,
|
|
cumulativeGasUsed: 10,
|
|
transactionIndex: 3,
|
|
blockNumber: 10,
|
|
gasUsed: 0
|
|
});
|
|
|
|
done();
|
|
});
|
|
|
|
});
|
|
it('should use on("receipt", ...) when subscribing and checking for receipt if "sendTransaction"', function (done) {
|
|
|
|
var send = succeedOnReceipt();
|
|
|
|
send({
|
|
from: '0x11f4d0A3c12e86B4b5F39B213F7E19D048276DAe',
|
|
to: '0x11f4d0A3c12e86B4b5F39B213F7E19D048276DAe',
|
|
value: '0xa'
|
|
}).on('receipt', function (result) {
|
|
|
|
|
|
assert.deepEqual(result, {
|
|
contractAddress: address,
|
|
cumulativeGasUsed: 10,
|
|
transactionIndex: 3,
|
|
blockNumber: 10,
|
|
gasUsed: 0
|
|
});
|
|
|
|
done();
|
|
});
|
|
|
|
});
|
|
|
|
|
|
var succeedwhenDeploying = function () {
|
|
var provider = new FakeHttpProvider();
|
|
var eth = new Eth(provider);
|
|
var method = new Method({
|
|
name: 'sendTransaction',
|
|
call: 'eth_sendTransaction',
|
|
params: 1,
|
|
inputFormatter: [formatters.inputTransactionFormatter]
|
|
});
|
|
method.setRequestManager(eth._requestManager, eth);
|
|
|
|
// generate send function
|
|
var send = method.buildCall();
|
|
|
|
// add results
|
|
provider.injectValidation(function (payload) {
|
|
assert.equal(payload.method, 'eth_sendTransaction');
|
|
assert.deepEqual(payload.params, [{
|
|
from: '0x11f4d0a3c12e86b4b5f39b213f7e19d048276dae',
|
|
data: '0xa123456'
|
|
}]);
|
|
});
|
|
provider.injectResult('0x1234567453543456321456321'); // tx hash
|
|
|
|
provider.injectValidation(function (payload) {
|
|
assert.equal(payload.method, 'eth_subscribe');
|
|
assert.deepEqual(payload.params, ['newHeads']);
|
|
});
|
|
provider.injectResult('0x1234567'); // subscription id
|
|
|
|
// fake newBlock
|
|
provider.injectNotification({
|
|
method: 'eth_subscription',
|
|
params: {
|
|
subscription: '0x1234567',
|
|
result: {
|
|
blockNumber: '0x10'
|
|
}
|
|
}
|
|
});
|
|
|
|
provider.injectValidation(function (payload) {
|
|
assert.equal(payload.method, 'eth_getTransactionReceipt');
|
|
assert.deepEqual(payload.params, ['0x1234567453543456321456321']);
|
|
});
|
|
// receipt
|
|
provider.injectResult({
|
|
contractAddress: address,
|
|
cumulativeGasUsed: '0xa',
|
|
transactionIndex: '0x3',
|
|
blockNumber: '0xa'
|
|
});
|
|
provider.injectValidation(function (payload) {
|
|
assert.equal(payload.method, 'eth_getCode');
|
|
assert.deepEqual(payload.params, [address, 'latest']);
|
|
});
|
|
// code result
|
|
provider.injectResult('0x321');
|
|
|
|
return send;
|
|
};
|
|
|
|
it('should use promise "then" when subscribing and checking for receipt and code if "sendTransaction" deploying contract', function (done) {
|
|
|
|
var send = succeedwhenDeploying();
|
|
|
|
send({
|
|
from: '0x11f4d0A3c12e86B4b5F39B213F7E19D048276DAe',
|
|
data: '0xa123456'
|
|
}).then(function (result) {
|
|
|
|
assert.deepEqual(result, {
|
|
contractAddress: address,
|
|
cumulativeGasUsed: 10,
|
|
transactionIndex: 3,
|
|
blockNumber: 10,
|
|
gasUsed: 0
|
|
});
|
|
|
|
done();
|
|
});
|
|
|
|
});
|
|
|
|
it('should use on("receipt", ...) when subscribing and checking for receipt and code if "sendTransaction" deploying contract', function (done) {
|
|
|
|
var send = succeedwhenDeploying();
|
|
|
|
send({
|
|
from: '0x11f4d0A3c12e86B4b5F39B213F7E19D048276DAe',
|
|
data: '0xa123456'
|
|
}).on('receipt', function (result) {
|
|
|
|
assert.deepEqual(result, {
|
|
contractAddress: address,
|
|
cumulativeGasUsed: 10,
|
|
transactionIndex: 3,
|
|
blockNumber: 10,
|
|
gasUsed: 0
|
|
});
|
|
|
|
done();
|
|
});
|
|
|
|
});
|
|
|
|
var failOnCodeEmpty = function () {
|
|
var provider = new FakeHttpProvider();
|
|
var eth = new Eth(provider);
|
|
var method = new Method({
|
|
name: 'sendTransaction',
|
|
call: 'eth_sendTransaction',
|
|
params: 1,
|
|
inputFormatter: [formatters.inputTransactionFormatter]
|
|
});
|
|
method.setRequestManager(eth._requestManager, eth);
|
|
|
|
// generate send function
|
|
var send = method.buildCall();
|
|
|
|
// add results
|
|
provider.injectValidation(function (payload) {
|
|
assert.equal(payload.method, 'eth_sendTransaction');
|
|
assert.deepEqual(payload.params, [{
|
|
from: '0x11f4d0a3c12e86b4b5f39b213f7e19d048276dae',
|
|
data: '0xa123456'
|
|
}]);
|
|
});
|
|
provider.injectResult('0x1234567453543456321456321'); // tx hash
|
|
|
|
provider.injectValidation(function (payload) {
|
|
assert.equal(payload.method, 'eth_subscribe');
|
|
assert.deepEqual(payload.params, ['newHeads']);
|
|
});
|
|
provider.injectResult('0x1234567'); // subscription id
|
|
|
|
// fake newBlock
|
|
provider.injectNotification({
|
|
method: 'eth_subscription',
|
|
params: {
|
|
subscription: '0x1234567',
|
|
result: {
|
|
blockNumber: '0x10'
|
|
}
|
|
}
|
|
});
|
|
|
|
provider.injectValidation(function (payload) {
|
|
assert.equal(payload.method, 'eth_getTransactionReceipt');
|
|
assert.deepEqual(payload.params, ['0x1234567453543456321456321']);
|
|
});
|
|
// receipt
|
|
provider.injectResult({
|
|
contractAddress: address,
|
|
cumulativeGasUsed: '0xa',
|
|
transactionIndex: '0x3',
|
|
blockNumber: '0xa'
|
|
});
|
|
provider.injectValidation(function (payload) {
|
|
assert.equal(payload.method, 'eth_getCode');
|
|
assert.deepEqual(payload.params, [address, 'latest']);
|
|
});
|
|
// code result
|
|
provider.injectResult('0x');
|
|
|
|
return send;
|
|
};
|
|
|
|
it('should fail on promise when subscribing and check for receipt and code if "sendTransaction" and deploying contract: error if code is empty', function (done) {
|
|
|
|
var send = failOnCodeEmpty();
|
|
|
|
send({
|
|
from: '0x11f4d0A3c12e86B4b5F39B213F7E19D048276DAe',
|
|
data: '0xa123456'
|
|
}).catch(function (error) {
|
|
assert.instanceOf(error, Error);
|
|
done();
|
|
});
|
|
|
|
});
|
|
|
|
it('should fail with on("error", ...) when subscribing and check for receipt and code if "sendTransaction" and deploying contract: error if code is empty', function (done) {
|
|
|
|
var send = failOnCodeEmpty();
|
|
|
|
send({
|
|
from: '0x11f4d0A3c12e86B4b5F39B213F7E19D048276DAe',
|
|
data: '0xa123456'
|
|
}).on('error', function (error) {
|
|
assert.instanceOf(error, Error);
|
|
done();
|
|
});
|
|
|
|
});
|
|
|
|
var failOnMissingAddress = function () {
|
|
var provider = new FakeHttpProvider();
|
|
var eth = new Eth(provider);
|
|
var method = new Method({
|
|
name: 'sendTransaction',
|
|
call: 'eth_sendTransaction',
|
|
params: 1,
|
|
inputFormatter: [formatters.inputTransactionFormatter]
|
|
});
|
|
method.setRequestManager(eth._requestManager, eth);
|
|
|
|
// generate send function
|
|
var send = method.buildCall();
|
|
|
|
// add results
|
|
provider.injectValidation(function (payload) {
|
|
assert.equal(payload.method, 'eth_sendTransaction');
|
|
assert.deepEqual(payload.params, [{
|
|
from: '0x11f4d0a3c12e86b4b5f39b213f7e19d048276dae',
|
|
data: '0xa123456'
|
|
}]);
|
|
});
|
|
provider.injectResult('0x1234567453543456321456321'); // tx hash
|
|
|
|
provider.injectValidation(function (payload) {
|
|
assert.equal(payload.method, 'eth_subscribe');
|
|
assert.deepEqual(payload.params, ['newHeads']);
|
|
});
|
|
provider.injectResult('0x1234567'); // subscription id
|
|
|
|
// fake newBlock
|
|
provider.injectNotification({
|
|
method: 'eth_subscription',
|
|
params: {
|
|
subscription: '0x1234567',
|
|
result: {
|
|
blockNumber: '0x10'
|
|
}
|
|
}
|
|
});
|
|
|
|
provider.injectValidation(function (payload) {
|
|
assert.equal(payload.method, 'eth_getTransactionReceipt');
|
|
assert.deepEqual(payload.params, ['0x1234567453543456321456321']);
|
|
});
|
|
// receipt
|
|
provider.injectResult({
|
|
contractAddress: null,
|
|
cumulativeGasUsed: '0xa',
|
|
transactionIndex: '0x3',
|
|
blockNumber: '0xa'
|
|
});
|
|
provider.injectValidation(function (payload) {
|
|
assert.equal(payload.method, 'eth_unsubscribe');
|
|
assert.deepEqual(payload.params, ['0x1234567']);
|
|
});
|
|
// code result
|
|
provider.injectResult(true);
|
|
|
|
return send;
|
|
};
|
|
|
|
it('should fail on promise when subscribing and check for receipt and code if "sendTransaction" and deploying contract: error if receipt has no contract address', function (done) {
|
|
var send = failOnMissingAddress();
|
|
|
|
send({
|
|
from: '0x11f4d0A3c12e86B4b5F39B213F7E19D048276DAe',
|
|
data: '0xa123456'
|
|
}).catch(function (error) {
|
|
assert.instanceOf(error, Error);
|
|
done();
|
|
});
|
|
|
|
});
|
|
it('should fail with on("error", ...) when subscribing and check for receipt and code if "sendTransaction" and deploying contract: error if receipt has no contract address', function (done) {
|
|
var send = failOnMissingAddress();
|
|
|
|
send({
|
|
from: '0x11f4d0A3c12e86B4b5F39B213F7E19D048276DAe',
|
|
data: '0xa123456'
|
|
}).on('error', function (error) {
|
|
assert.instanceOf(error, Error);
|
|
}).catch(function (error) {
|
|
// also run catch!
|
|
assert.instanceOf(error, Error);
|
|
done();
|
|
});
|
|
|
|
});
|
|
|
|
var failOnTimeout = function () {
|
|
var provider = new FakeHttpProvider();
|
|
var eth = new Eth(provider);
|
|
var method = new Method({
|
|
name: 'sendTransaction',
|
|
call: 'eth_sendTransaction',
|
|
params: 1,
|
|
inputFormatter: [formatters.inputTransactionFormatter]
|
|
});
|
|
method.setRequestManager(eth._requestManager, eth);
|
|
|
|
// generate send function
|
|
var send = method.buildCall();
|
|
|
|
// add results
|
|
provider.injectValidation(function (payload) {
|
|
assert.equal(payload.method, 'eth_sendTransaction');
|
|
assert.deepEqual(payload.params, [{
|
|
from: '0x11f4d0a3c12e86b4b5f39b213f7e19d048276dae',
|
|
data: '0xa123456'
|
|
}]);
|
|
});
|
|
provider.injectResult('0x1234567453543456321456321'); // tx hash
|
|
|
|
provider.injectValidation(function (payload) {
|
|
assert.equal(payload.method, 'eth_subscribe');
|
|
assert.deepEqual(payload.params, ['newHeads']);
|
|
});
|
|
provider.injectResult('0x1234567'); // subscription id
|
|
|
|
// fire 50 fake newBlocks
|
|
for (i = 0; i < 51; i++) {
|
|
setTimeout(function () {
|
|
provider.injectNotification({
|
|
method: 'eth_subscription',
|
|
params: {
|
|
subscription: '0x1234567',
|
|
result: {
|
|
blockNumber: '0x10'
|
|
}
|
|
}
|
|
});
|
|
},i);
|
|
|
|
// receipt
|
|
provider.injectResult(null);
|
|
}
|
|
|
|
provider.injectValidation(function (payload) {
|
|
assert.equal(payload.method, 'eth_getTransactionReceipt');
|
|
assert.deepEqual(payload.params, ['0x1234567453543456321456321']);
|
|
});
|
|
|
|
return send;
|
|
|
|
};
|
|
|
|
it('should fail with promise when subscribing and check for receipt and code if "sendTransaction" and deploying contract: if not receipt after 50 blocks', function (done) {
|
|
var send = failOnTimeout();
|
|
|
|
send({
|
|
from: '0x11f4d0A3c12e86B4b5F39B213F7E19D048276DAe',
|
|
data: '0xa123456'
|
|
}).catch(function (error) {
|
|
assert.instanceOf(error, Error);
|
|
done();
|
|
});
|
|
});
|
|
it('should fail with on("error", ...) when subscribing and check for receipt and code if "sendTransaction" and deploying contract: if not receipt after 50 blocks', function (done) {
|
|
var send = failOnTimeout();
|
|
|
|
send({
|
|
from: '0x11f4d0A3c12e86B4b5F39B213F7E19D048276DAe',
|
|
data: '0xa123456'
|
|
}).on('error', function (error) {
|
|
assert.instanceOf(error, Error);
|
|
done();
|
|
});
|
|
|
|
});
|
|
|
|
it('should give confirmation receipts with on("confirmation", ...) when subscribing "sendTransaction"', function (done) {
|
|
var provider = new FakeHttpProvider();
|
|
var eth = new Eth(provider);
|
|
var method = new Method({
|
|
name: 'sendTransaction',
|
|
call: 'eth_sendTransaction',
|
|
params: 1,
|
|
inputFormatter: [formatters.inputTransactionFormatter]
|
|
});
|
|
method.setRequestManager(eth._requestManager, eth);
|
|
|
|
// generate send function
|
|
var send = method.buildCall();
|
|
|
|
// add results
|
|
provider.injectValidation(function (payload) {
|
|
assert.equal(payload.method, 'eth_sendTransaction');
|
|
assert.deepEqual(payload.params, [{
|
|
from: '0x11f4d0a3c12e86b4b5f39b213f7e19d048276dae',
|
|
to: '0x11f4d0a3c12e86b4b5f39b213f7e19d048276dae'
|
|
}]);
|
|
});
|
|
provider.injectResult('0x1234567453543456321456321'); // tx hash
|
|
|
|
provider.injectValidation(function (payload) {
|
|
assert.equal(payload.method, 'eth_subscribe');
|
|
assert.deepEqual(payload.params, ['newHeads']);
|
|
});
|
|
provider.injectResult('0x1234567'); // subscription id
|
|
|
|
// fire 50 fake newBlocks
|
|
for (i = 0; i < 30; i++) {
|
|
|
|
setTimeout(function () {
|
|
provider.injectNotification({
|
|
method: 'eth_subscription',
|
|
params: {
|
|
subscription: '0x1234567',
|
|
result: {
|
|
blockNumber: '0x10'
|
|
}
|
|
}
|
|
});
|
|
}, i);
|
|
|
|
// receipt
|
|
provider.injectResult({
|
|
contractAddress: null,
|
|
cumulativeGasUsed: '0xa',
|
|
transactionIndex: '0x3',
|
|
blockNumber: '0xa'
|
|
});
|
|
}
|
|
|
|
provider.injectValidation(function (payload) {
|
|
assert.equal(payload.method, 'eth_getTransactionReceipt');
|
|
assert.deepEqual(payload.params, ['0x1234567453543456321456321']);
|
|
});
|
|
|
|
|
|
var countConf = 0;
|
|
|
|
send({
|
|
from: '0x11f4d0A3c12e86B4b5F39B213F7E19D048276DAe',
|
|
to: '0x11f4d0A3c12e86B4b5F39B213F7E19D048276DAe'
|
|
})
|
|
.on('transactionHash', function(result){
|
|
assert.deepEqual(result, '0x1234567453543456321456321');
|
|
})
|
|
.on('receipt', function(result){
|
|
|
|
assert.deepEqual(result, {
|
|
contractAddress: null,
|
|
cumulativeGasUsed: 10,
|
|
transactionIndex: 3,
|
|
blockNumber: 10,
|
|
gasUsed: 0
|
|
});
|
|
|
|
})
|
|
.on('confirmation', function (conf, receipt) {
|
|
|
|
assert.deepEqual(receipt, {
|
|
contractAddress: null,
|
|
cumulativeGasUsed: 10,
|
|
transactionIndex: 3,
|
|
blockNumber: 10,
|
|
gasUsed: 0
|
|
});
|
|
|
|
assert.deepEqual(conf, countConf);
|
|
|
|
countConf++;
|
|
|
|
if(conf === 12) {
|
|
done();
|
|
}
|
|
});
|
|
|
|
});
|
|
});
|
|
});
|
|
|