commit
6884d6dc2a
@ -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