commit
f4fa44a982
@ -0,0 +1,256 @@ |
||||
var remixLib = require('remix-lib') |
||||
var EventManager = remixLib.EventManager |
||||
var ethutil = require('ethereumjs-util') |
||||
var executionContext = require('./execution-context') |
||||
var format = require('./app/execution/txFormat') |
||||
var txHelper = require('./app/execution/txHelper') |
||||
var async = require('async') |
||||
var modal = require('./app/ui/modal-dialog-custom') |
||||
|
||||
/** |
||||
* Record transaction as long as the user create them. |
||||
* |
||||
* |
||||
*/ |
||||
class Recorder { |
||||
constructor (opts = {}) { |
||||
var self = this |
||||
self._api = opts.api |
||||
self.event = new EventManager() |
||||
self.data = { _listen: true, _replay: false, journal: [], _createdContracts: {}, _createdContractsReverse: {}, _usedAccounts: {}, _abis: {}, _contractABIReferences: {}, _linkReferences: {} } |
||||
opts.events.executioncontext.register('contextChanged', () => { |
||||
self.clearAll() |
||||
}) |
||||
opts.events.runtab.register('clearInstances', () => { |
||||
self.clearAll() |
||||
}) |
||||
|
||||
opts.events.udapp.register('initiatingTransaction', (timestamp, tx, payLoad) => { |
||||
if (tx.useCall) return |
||||
var { from, to, value } = tx |
||||
|
||||
// convert to and from to tokens
|
||||
if (this.data._listen) { |
||||
var record = { value, parameters: payLoad.funArgs } |
||||
if (!to) { |
||||
var selectedContract = self._api.getContract(payLoad.contractName) |
||||
if (selectedContract) { |
||||
var abi = selectedContract.object.abi |
||||
var sha3 = ethutil.bufferToHex(ethutil.sha3(abi)) |
||||
record.abi = sha3 |
||||
record.contractName = payLoad.contractName |
||||
record.bytecode = payLoad.contractBytecode |
||||
record.linkReferences = selectedContract.object.evm.bytecode.linkReferences |
||||
if (Object.keys(record.linkReferences).length) { |
||||
for (var file in record.linkReferences) { |
||||
for (var lib in record.linkReferences[file]) { |
||||
self.data._linkReferences[lib] = '<address>' |
||||
} |
||||
} |
||||
} |
||||
self.data._abis[sha3] = abi |
||||
|
||||
this.data._contractABIReferences[timestamp] = sha3 |
||||
} |
||||
} else { |
||||
var creationTimestamp = this.data._createdContracts[to] |
||||
record.to = `created{${creationTimestamp}}` |
||||
record.abi = this.data._contractABIReferences[creationTimestamp] |
||||
} |
||||
|
||||
record.name = payLoad.funAbi.name |
||||
record.type = payLoad.funAbi.type |
||||
|
||||
self._api.getAccounts((error, accounts) => { |
||||
if (error) return console.log(error) |
||||
record.from = `account{${accounts.indexOf(from)}}` |
||||
self.data._usedAccounts[record.from] = from |
||||
self.append(timestamp, record) |
||||
}) |
||||
} |
||||
}) |
||||
|
||||
opts.events.udapp.register('transactionExecuted', (error, from, to, data, call, txResult, timestamp) => { |
||||
if (error) return console.log(error) |
||||
if (call) return |
||||
|
||||
var address = executionContext.isVM() ? txResult.result.createdAddress : txResult.result.contractAddress |
||||
if (!address) return // not a contract creation
|
||||
address = addressToString(address) |
||||
// save back created addresses for the convertion from tokens to real adresses
|
||||
this.data._createdContracts[address] = timestamp |
||||
this.data._createdContractsReverse[timestamp] = address |
||||
}) |
||||
} |
||||
|
||||
/** |
||||
* stop/start saving txs. If not listenning, is basically in replay mode |
||||
* |
||||
* @param {Bool} listen |
||||
*/ |
||||
setListen (listen) { |
||||
this.data._listen = listen |
||||
this.data._replay = !listen |
||||
} |
||||
|
||||
extractTimestamp (value) { |
||||
var stamp = /created{(.*)}/g.exec(value) |
||||
if (stamp) { |
||||
return stamp[1] |
||||
} |
||||
return null |
||||
} |
||||
|
||||
/** |
||||
* convert back from/to from tokens to real addresses |
||||
* |
||||
* @param {Object} record |
||||
* @param {Object} accounts |
||||
* @param {Object} options |
||||
* |
||||
*/ |
||||
resolveAddress (record, accounts, options) { |
||||
if (record.to) { |
||||
var stamp = this.extractTimestamp(record.to) |
||||
if (stamp) { |
||||
record.to = this.data._createdContractsReverse[stamp] |
||||
} |
||||
} |
||||
record.from = accounts[record.from] |
||||
// @TODO: writing browser test
|
||||
return record |
||||
} |
||||
|
||||
/** |
||||
* save the given @arg record |
||||
* |
||||
* @param {Number/String} timestamp |
||||
* @param {Object} record |
||||
* |
||||
*/ |
||||
append (timestamp, record) { |
||||
var self = this |
||||
self.data.journal.push({ timestamp, record }) |
||||
} |
||||
|
||||
/** |
||||
* basically return the records + associate values (like abis / accounts) |
||||
* |
||||
*/ |
||||
getAll () { |
||||
var self = this |
||||
var records = [].concat(self.data.journal) |
||||
return { |
||||
accounts: self.data._usedAccounts, |
||||
linkReferences: self.data._linkReferences, |
||||
transactions: records.sort((A, B) => { |
||||
var stampA = A.timestamp |
||||
var stampB = B.timestamp |
||||
return stampA - stampB |
||||
}), |
||||
abis: self.data._abis |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* delete the seen transactions |
||||
* |
||||
*/ |
||||
clearAll () { |
||||
var self = this |
||||
self.data._listen = true |
||||
self.data._replay = false |
||||
self.data.journal = [] |
||||
self.data._createdContracts = {} |
||||
self.data._createdContractsReverse = {} |
||||
self.data._usedAccounts = {} |
||||
self.data._abis = {} |
||||
self.data._contractABIReferences = {} |
||||
self.data._linkReferences = {} |
||||
} |
||||
|
||||
/** |
||||
* run the list of records |
||||
* |
||||
* @param {Object} accounts |
||||
* @param {Object} options |
||||
* @param {Object} abis |
||||
* @param {Function} newContractFn |
||||
* |
||||
*/ |
||||
run (records, accounts, options, abis, linkReferences, newContractFn) { |
||||
var self = this |
||||
self.setListen(false) |
||||
self._api.logMessage(`Running ${records.length} transaction(s) ...`) |
||||
async.eachOfSeries(records, function (tx, index, cb) { |
||||
var record = self.resolveAddress(tx.record, accounts, options) |
||||
var abi = abis[tx.record.abi] |
||||
if (!abi) { |
||||
modal.alert('cannot find ABI for ' + tx.record.abi + '. Execution stopped at ' + index) |
||||
return |
||||
} |
||||
/* Resolve Library */ |
||||
if (record.linkReferences && Object.keys(record.linkReferences).length) { |
||||
for (var k in linkReferences) { |
||||
var link = linkReferences[k] |
||||
var timestamp = self.extractTimestamp(link) |
||||
if (timestamp && self.data._createdContractsReverse[timestamp]) { |
||||
link = self.data._createdContractsReverse[timestamp] |
||||
} |
||||
tx.record.bytecode = format.linkLibraryStandardFromlinkReferences(k, link.replace('0x', ''), tx.record.bytecode, tx.record.linkReferences) |
||||
} |
||||
} |
||||
/* Encode params */ |
||||
var fnABI |
||||
if (tx.record.type === 'constructor') { |
||||
fnABI = txHelper.getConstructorInterface(abi) |
||||
} else { |
||||
fnABI = txHelper.getFunction(abi, record.name) |
||||
} |
||||
if (!fnABI) { |
||||
modal.alert('cannot resolve abi of ' + JSON.stringify(record, null, '\t') + '. Execution stopped at ' + index) |
||||
cb('cannot resolve abi') |
||||
return |
||||
} |
||||
var data = format.encodeData(fnABI, tx.record.parameters, tx.record.bytecode) |
||||
if (data.error) { |
||||
modal.alert(data.error + '. Record:' + JSON.stringify(record, null, '\t') + '. Execution stopped at ' + index) |
||||
cb(data.error) |
||||
return |
||||
} else { |
||||
self._api.logMessage(`(${index}) ${JSON.stringify(record, null, '\t')}`) |
||||
self._api.logMessage(`(${index}) data: ${data.data}`) |
||||
record.data = { dataHex: data.data, funArgs: tx.record.parameters, funAbi: fnABI, contractBytecode: tx.record.bytecode, contractName: tx.record.contractName } |
||||
} |
||||
self._api.udapp().runTx(record, function (err, txResult) { |
||||
if (err) { |
||||
console.error(err) |
||||
self._api.logMessage(err + '. Execution failed at ' + index) |
||||
} else { |
||||
var address = executionContext.isVM() ? txResult.result.createdAddress : txResult.result.contractAddress |
||||
if (address) { |
||||
address = addressToString(address) |
||||
// save back created addresses for the convertion from tokens to real adresses
|
||||
self.data._createdContracts[address] = tx.timestamp |
||||
self.data._createdContractsReverse[tx.timestamp] = address |
||||
newContractFn(abi, address, record.contractName) |
||||
} |
||||
} |
||||
cb(err) |
||||
}) |
||||
}, () => { self.setListen(true); self.clearAll() }) |
||||
} |
||||
} |
||||
|
||||
function addressToString (address) { |
||||
if (!address) return null |
||||
if (typeof address !== 'string') { |
||||
address = address.toString('hex') |
||||
} |
||||
if (address.indexOf('0x') === -1) { |
||||
address = '0x' + address |
||||
} |
||||
return address |
||||
} |
||||
|
||||
module.exports = Recorder |
@ -0,0 +1,172 @@ |
||||
'use strict' |
||||
var contractHelper = require('../../helpers/contracts') |
||||
|
||||
module.exports = function (browser, callback) { |
||||
contractHelper.addFile(browser, 'scenario.json', {content: records}, () => { |
||||
browser |
||||
.click('.runView') |
||||
.click('#runTabView .runtransaction') |
||||
.clickFunction('getInt - call') |
||||
.clickFunction('getAddress - call') |
||||
.clickFunction('getFromLib - call') |
||||
.waitForElementPresent('div[class^="contractProperty"] div[class^="value"]') |
||||
.perform(() => { |
||||
contractHelper.verifyCallReturnValue(browser, '0x35ef07393b57464e93deb59175ff72e6499450cf', ['0: uint256: 1', '0: uint256: 3456', '0: address: 0xca35b7d915458ef540ade6068dfe2f44e8fa733c'], () => { callback() }) |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
var records = `{
|
||||
"accounts": { |
||||
"account{0}": "0xca35b7d915458ef540ade6068dfe2f44e8fa733c" |
||||
}, |
||||
"linkReferences": { |
||||
"testLib": "created{1512830014773}" |
||||
}, |
||||
"transactions": [ |
||||
{ |
||||
"timestamp": 1512830014773, |
||||
"record": { |
||||
"value": "0", |
||||
"parameters": [], |
||||
"abi": "0xbc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a", |
||||
"contractName": "testLib", |
||||
"bytecode": "60606040523415600e57600080fd5b60968061001c6000396000f300606060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680636d4ce63c146044575b600080fd5b604a6060565b6040518082815260200191505060405180910390f35b6000610d809050905600a165627a7a7230582022d123b15248b8176151f8d45c2dc132063bcc9bb8d5cd652aea7efae362c8050029", |
||||
"linkReferences": {}, |
||||
"type": "constructor", |
||||
"from": "account{0}" |
||||
} |
||||
}, |
||||
{ |
||||
"timestamp": 1512830015080, |
||||
"record": { |
||||
"value": "100", |
||||
"parameters": [ |
||||
11 |
||||
], |
||||
"abi": "0xc41589e7559804ea4a2080dad19d876a024ccb05117835447d72ce08c1d020ec", |
||||
"contractName": "test", |
||||
"bytecode": "60606040526040516020806102b183398101604052808051906020019091905050806000819055505061027a806100376000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632f30c6f61461006757806338cc48311461009e57806362738998146100f357806387cc10e11461011c575b600080fd5b61009c600480803590602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610145565b005b34156100a957600080fd5b6100b1610191565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156100fe57600080fd5b6101066101bb565b6040518082815260200191505060405180910390f35b341561012757600080fd5b61012f6101c4565b6040518082815260200191505060405180910390f35b8160008190555080600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050565b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60008054905090565b600073__browser/ballot.sol:testLib____________636d4ce63c6000604051602001526040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160206040518083038186803b151561022e57600080fd5b6102c65a03f4151561023f57600080fd5b505050604051805190509050905600a165627a7a72305820e0b2510bb2890a0334bfe5613d96db3e72442e63b514cdeaee8fc2c6bbd19d3a0029", |
||||
"linkReferences": { |
||||
"browser/ballot.sol": { |
||||
"testLib": [ |
||||
{ |
||||
"length": 20, |
||||
"start": 511 |
||||
} |
||||
] |
||||
} |
||||
}, |
||||
"name": "", |
||||
"type": "constructor", |
||||
"from": "account{0}" |
||||
} |
||||
}, |
||||
{ |
||||
"timestamp": 1512830034180, |
||||
"record": { |
||||
"value": "1000000000000000000", |
||||
"parameters": [ |
||||
1, |
||||
"0xca35b7d915458ef540ade6068dfe2f44e8fa733c" |
||||
], |
||||
"to": "created{1512830015080}", |
||||
"abi": "0xc41589e7559804ea4a2080dad19d876a024ccb05117835447d72ce08c1d020ec", |
||||
"name": "set", |
||||
"type": "function", |
||||
"from": "account{0}" |
||||
} |
||||
} |
||||
], |
||||
"abis": { |
||||
"0xbc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a": [ |
||||
{ |
||||
"constant": true, |
||||
"inputs": [], |
||||
"name": "get", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
} |
||||
], |
||||
"0xc41589e7559804ea4a2080dad19d876a024ccb05117835447d72ce08c1d020ec": [ |
||||
{ |
||||
"constant": true, |
||||
"inputs": [], |
||||
"name": "getInt", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [], |
||||
"name": "getFromLib", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [], |
||||
"name": "getAddress", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": false, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_t", |
||||
"type": "uint256" |
||||
}, |
||||
{ |
||||
"name": "_add", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "set", |
||||
"outputs": [], |
||||
"payable": true, |
||||
"stateMutability": "payable", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"inputs": [ |
||||
{ |
||||
"name": "_r", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": true, |
||||
"stateMutability": "payable", |
||||
"type": "constructor" |
||||
} |
||||
] |
||||
} |
||||
}` |
Loading…
Reference in new issue