Commit 952ee12d authored by yann300's avatar yann300

move instr convertion to js

parent a4f9d254
var React = require('react'); var React = require('react');
var BasicPanel = require('./basicPanel'); var BasicPanel = require('./basicPanel')
var codeUtils = require('./codeUtils')
var style = require('./basicStyles') var style = require('./basicStyles')
module.exports = React.createClass({ module.exports = React.createClass({
...@@ -7,18 +8,20 @@ module.exports = React.createClass({ ...@@ -7,18 +8,20 @@ module.exports = React.createClass({
getInitialState: function() getInitialState: function()
{ {
return { return {
currentSelected: 0, currentSelected: 0, // current selected item in the vmTrace
selectedInst: 0, // current selected item in the contract assembly code
currentAddress: null, currentAddress: null,
currentStack: null, currentStack: null,
currentLevels: null, currentLevels: null,
currentStorage: null, currentStorage: null,
currentMemory: null, currentMemory: null,
currentCallData: null, currentCallData: null,
selectedInst: 0,
lastLevels: null, lastLevels: null,
lastStorage: null, lastStorage: null,
lastMemory: null, lastMemory: null,
lastCallData: null lastCallData: null,
codes: {},
codesMap: {}
}; };
}, },
...@@ -33,6 +36,7 @@ module.exports = React.createClass({ ...@@ -33,6 +36,7 @@ module.exports = React.createClass({
{ {
return ( return (
<div style={this.props.vmTrace === null ? style.hidden : style.display} > <div style={this.props.vmTrace === null ? style.hidden : style.display} >
<div style={style.container}><span style={style.address}>{this.state.currentAddress}</span></div>
<div style={style.container}> <div style={style.container}>
<button onClick={this.stepIntoBack} disabled={ this.checkButtonState(-1) } >stepIntoBack</button> <button onClick={this.stepIntoBack} disabled={ this.checkButtonState(-1) } >stepIntoBack</button>
<button onClick={this.stepOverBack} disabled={ this.checkButtonState(-1) } >stepOverBack</button> <button onClick={this.stepOverBack} disabled={ this.checkButtonState(-1) } >stepOverBack</button>
...@@ -40,7 +44,7 @@ module.exports = React.createClass({ ...@@ -40,7 +44,7 @@ module.exports = React.createClass({
<button onClick={this.stepIntoForward} disabled={ this.checkButtonState(1) } >stepIntoForward</button> <button onClick={this.stepIntoForward} disabled={ this.checkButtonState(1) } >stepIntoForward</button>
</div> </div>
<div style={style.container}> <div style={style.container}>
<select size="10" ref='itemsList' value={this.state.selectedInst}> <select size="10" ref='itemsList' style={style.container} value={this.state.selectedInst}>
{ this.renderAssemblyItems() } { this.renderAssemblyItems() }
</select> </select>
</div> </div>
...@@ -48,13 +52,38 @@ module.exports = React.createClass({ ...@@ -48,13 +52,38 @@ module.exports = React.createClass({
<BasicPanel name="Stack" data={this.state.currentStack} /> <BasicPanel name="Stack" data={this.state.currentStack} />
<BasicPanel name="CallStack" data={this.state.currentCallStack} /> <BasicPanel name="CallStack" data={this.state.currentCallStack} />
<BasicPanel name="Storage" data={this.state.currentStorage} /> <BasicPanel name="Storage" data={this.state.currentStorage} />
<BasicPanel name="Memory" data={this.state.currentMemory} /> <BasicPanel name="Memory" data={this.state.currentMemory} renderRow={this.renderMemoryRow} />
<BasicPanel name="CallData" data={this.state.currentCallData} /> <BasicPanel name="CallData" data={this.state.currentCallData} />
</div> </div>
</div> </div>
); );
}, },
renderMemoryRow: function(data)
{
var ret = []
if (data)
{
for (var key in data)
{
var memSlot = data[key]
ret.push(<tr key={key} ><td>{memSlot.address}</td><td>{memSlot.content.raw}</td><td>{memSlot.content.ascii}</td></tr>)
}
}
return ret
},
resolveAddress: function(address)
{
if (!this.state.codes[address])
{
var hexCode = web3.eth.getCode(address)
var code = codeUtils.nameOpCodes(new Buffer(hexCode.substring(2), 'hex'))
this.state.codes[address] = code[0]
this.state.codesMap[address] = code[1]
}
},
checkButtonState: function(incr) checkButtonState: function(incr)
{ {
if (!this.props.vmTrace) if (!this.props.vmTrace)
...@@ -62,71 +91,82 @@ module.exports = React.createClass({ ...@@ -62,71 +91,82 @@ module.exports = React.createClass({
if (incr === -1) if (incr === -1)
return this.state.currentSelected === 0 ? "disabled" : "" return this.state.currentSelected === 0 ? "disabled" : ""
else if (incr === 1) else if (incr === 1)
return this.state.currentSelected >= this.props.vmTrace.vmtrace.length - 1 ? "disabled" : "" return this.state.currentSelected >= this.props.vmTrace.length - 1 ? "disabled" : ""
}, },
renderAssemblyItems: function() renderAssemblyItems: function()
{ {
if (this.props.vmTrace && this.props.vmTrace.vmtrace) if (this.props.vmTrace)
{ {
return this.props.vmTrace.vmtrace.map(function(item, i) return this.state.codes[this.state.currentAddress].map(function(item, i)
{ {
return <option key={i} value={i} >{i} {item.instname}</option>; return <option key={i} value={i} >{item}</option>;
}); });
} }
}, },
componentWillReceiveProps: function (nextProps) { componentWillReceiveProps: function (nextProps)
{
this.updateState(nextProps, 0) this.updateState(nextProps, 0)
}, },
updateState: function(props, vmTraceIndex) updateState: function(props, vmTraceIndex)
{ {
var previousState = this.state.currentSelected var previousState = this.state.currentSelected
this.setState({ currentSelected: vmTraceIndex }) var stateChanges = {}
this.state.currentAddress = props.vmTrace.vmtrace[0].address
this.state.selectedInst = props.vmTrace.codesmap[this.state.currentAddress][props.vmTrace.vmtrace[vmTraceIndex].pc] var currentAddress = this.state.currentAddress
if (!currentAddress)
currentAddress = props.vmTrace[vmTraceIndex].address
if (props.vmTrace[vmTraceIndex].address && props.vmTrace[vmTraceIndex].address !== this.state.currentAddress)
{
this.resolveAddress(props.vmTrace[vmTraceIndex].address)
stateChanges["currentAddress"] = props.vmTrace[vmTraceIndex].address
}
if (props.vmTrace.vmtrace[vmTraceIndex].stack) if (props.vmTrace[vmTraceIndex].stack)
{ {
var stack = props.vmTrace.vmtrace[vmTraceIndex].stack var stack = props.vmTrace[vmTraceIndex].stack
stack.reverse() stack.reverse()
this.setState({ currentStack: stack }) stateChanges["currentStack"] = stack
} }
if (props.vmTrace.vmtrace[vmTraceIndex].levels) if (props.vmTrace[vmTraceIndex].levels)
{ {
var levels = props.vmTrace.vmtrace[vmTraceIndex].levels var levels = props.vmTrace[vmTraceIndex].levels
var callStack = [] var callStack = []
for (var k in levels) for (var k in levels)
callStack.push(props.vmTrace.vmtrace[levels[k]].address) callStack.push(props.vmTrace[levels[k]].address)
this.setState({ currentCallStack: callStack }) stateChanges["currentCallStack"] = callStack
lastCallStack = callStack lastCallStack = callStack
} }
var storageIndex = vmTraceIndex var storageIndex = vmTraceIndex
if (vmTraceIndex < previousState) if (vmTraceIndex < previousState)
storageIndex = this.retrieveLastSeenProperty(vmTraceIndex, "storage", props.vmTrace.vmtrace) storageIndex = this.retrieveLastSeenProperty(vmTraceIndex, "storage", props.vmTrace)
if (props.vmTrace.vmtrace[storageIndex].storage || storageIndex === 0) if (props.vmTrace[storageIndex].storage || storageIndex === 0)
{ {
this.setState({ currentStorage: props.vmTrace.vmtrace[storageIndex].storage }) stateChanges["currentStorage"] = props.vmTrace[storageIndex].storage
lastStorage = props.vmTrace.vmtrace[storageIndex].storage lastStorage = props.vmTrace[storageIndex].storage
} }
var memoryIndex = vmTraceIndex var memoryIndex = vmTraceIndex
if (vmTraceIndex < previousState) if (vmTraceIndex < previousState)
memoryIndex = this.retrieveLastSeenProperty(vmTraceIndex, "memory", props.vmTrace.vmtrace) memoryIndex = this.retrieveLastSeenProperty(vmTraceIndex, "memory", props.vmTrace)
if (props.vmTrace.vmtrace[memoryIndex].memory || memoryIndex === 0) if (props.vmTrace[memoryIndex].memory || memoryIndex === 0)
{ {
this.setState({ currentMemory: this.formatMemory(props.vmTrace.vmtrace[memoryIndex].memory, 16) }) stateChanges["currentMemory"] = this.formatMemory(props.vmTrace[memoryIndex].memory, 16)
lastMemory = this.formatMemory(props.vmTrace.vmtrace[memoryIndex].memory, 16) lastMemory = this.formatMemory(props.vmTrace[memoryIndex].memory, 16)
} }
if (props.vmTrace.vmtrace[vmTraceIndex].calldata) if (props.vmTrace[vmTraceIndex].calldata)
{ {
this.setState({ currentCallData: props.vmTrace.vmtrace[vmTraceIndex].calldata }) stateChanges["currentCallData"] = props.vmTrace[vmTraceIndex].calldata
lastCallData = props.vmTrace.vmtrace[vmTraceIndex].calldata lastCallData = props.vmTrace[vmTraceIndex].calldata
} }
stateChanges["selectedInst"] = this.state.codesMap[currentAddress][props.vmTrace[vmTraceIndex].pc]
stateChanges["currentSelected"] = vmTraceIndex
this.setState(stateChanges)
}, },
retrieveLastSeenProperty: function(currentIndex, propertyName, vmTrace) retrieveLastSeenProperty: function(currentIndex, propertyName, vmTrace)
...@@ -185,13 +225,13 @@ module.exports = React.createClass({ ...@@ -185,13 +225,13 @@ module.exports = React.createClass({
isCallInstruction: function(index) isCallInstruction: function(index)
{ {
var state = this.props.vmTrace.vmtrace[index]; var state = this.props.vmTrace[index];
return state.instname === "CALL" || state.instname === "CREATE"; return state.instname === "CALL" || state.instname === "CREATE" || state.instname === "DELEGATECALL"
}, },
isReturnInstruction: function(index) isReturnInstruction: function(index)
{ {
var state = this.props.vmTrace.vmtrace[index]; var state = this.props.vmTrace[index];
return state.instname === "RETURN" return state.instname === "RETURN"
}, },
...@@ -219,7 +259,7 @@ module.exports = React.createClass({ ...@@ -219,7 +259,7 @@ module.exports = React.createClass({
{ {
var i = this.state.currentSelected var i = this.state.currentSelected
var depth = 0 var depth = 0
while (++i < this.props.vmTrace.vmtrace.length) while (++i < this.props.vmTrace.length)
{ {
if (this.isReturnInstruction(i)) if (this.isReturnInstruction(i))
{ {
...@@ -241,11 +281,7 @@ module.exports = React.createClass({ ...@@ -241,11 +281,7 @@ module.exports = React.createClass({
selectState: function(index) selectState: function(index)
{ {
var selectedInst = this.props.vmTrace.codesmap[this.state.currentAddress][this.props.vmTrace.vmtrace[index].pc]
this.updateState(this.props, index) this.updateState(this.props, index)
this.refs.itemsList.value = selectedInst
if (this.props.vmTrace.vmtrace[index].address && this.state.currentAddress !== this.props.vmTrace.vmtrace[index].address)
this.state.currentAddress = this.props.vmTrace.vmtrace[index].address
}, },
formatMemory: function(mem, width) formatMemory: function(mem, width)
......
...@@ -7,7 +7,8 @@ module.exports = React.createClass({ ...@@ -7,7 +7,8 @@ module.exports = React.createClass({
{ {
return { return {
data: null, data: null,
name: null name: null,
renderRow: null
}; };
}, },
...@@ -18,7 +19,7 @@ module.exports = React.createClass({ ...@@ -18,7 +19,7 @@ module.exports = React.createClass({
<div style={style.panel.title} >{this.props.name}</div> <div style={style.panel.title} >{this.props.name}</div>
<table style={style.panel.table}> <table style={style.panel.table}>
<tbody> <tbody>
{ this.renderItems() } {this.renderItems()}
</tbody> </tbody>
</table> </table>
</div> </div>
...@@ -27,12 +28,16 @@ module.exports = React.createClass({ ...@@ -27,12 +28,16 @@ module.exports = React.createClass({
renderItems: function() renderItems: function()
{ {
var ret = [] if (!this.props.data)
if (this.props.data) return []
if (!this.props.renderRow)
{ {
var ret = []
for (var key in this.props.data) for (var key in this.props.data)
ret.push(<tr key={key} ><td>{JSON.stringify(this.props.data[key])}</td></tr>) ret.push(<tr key={key} ><td>{this.props.data[key]}</td></tr>)
}
return ret return ret
} }
else
return this.props.renderRow(this.props.data)
}
}) })
module.exports = { module.exports = {
wrapper:
{
'fontFamily': "arial,sans-serif"
},
container: container:
{ {
margin: '10px', margin: '10px',
padding: '5px' padding: '5px'
}, },
address:
{
'fontStyle': 'italic'
},
instructions:
{
'width': '600px'
},
panel: panel:
{ {
container: container:
{ {
margin: '10px', margin: '10px',
border: '1px solid', border: '1px solid',
width: '800px' width: '600px'
}, },
table: table:
{ {
...@@ -18,7 +30,8 @@ module.exports = { ...@@ -18,7 +30,8 @@ module.exports = {
}, },
title: title:
{ {
padding: '5px' padding: '5px',
'fontStyle': 'italic'
} }
}, },
hidden: hidden:
......
var opcodes = require('./opcodes')
module.exports = {
nameOpCodes: function (raw)
{
var pushData = ''
var codeMap = {}
var code = []
for (var i = 0; i < raw.length; i++)
{
var pc = i
var curOpCode = opcodes(raw[pc], false).name
codeMap[i] = code.length
// no destinations into the middle of PUSH
if (curOpCode.slice(0, 4) === 'PUSH')
{
var jumpNum = raw[pc] - 0x5f
pushData = raw.slice(pc + 1, pc + jumpNum + 1)
i += jumpNum
}
code.push(this.pad(pc, this.roundLog(raw.length, 10)) + ' ' + curOpCode + ' ' + pushData.toString('hex'))
pushData = ''
}
return [ code, codeMap ]
},
pad: function (num, size) {
var s = num + ''
while (s.length < size) s = '0' + s
return s
},
log: function (num, base) {
return Math.log(num) / Math.log(base)
},
roundLog: function (num, base) {
return Math.ceil(this.log(num, base))
}
}
\ No newline at end of file
...@@ -11,8 +11,8 @@ module.exports = React.createClass({ ...@@ -11,8 +11,8 @@ module.exports = React.createClass({
render: function() { render: function() {
return ( return (
<div> <div style={style.wrapper} >
<h1 style={style.container} >Debugger</h1> <h1 style={style.container} >Eth Debugger</h1>
<TxBrowser onNewTxRequested={this.retrieveVmTrace} /> <TxBrowser onNewTxRequested={this.retrieveVmTrace} />
<VmTraceBrowser vmTrace={this.state.vmTrace} /> <VmTraceBrowser vmTrace={this.state.vmTrace} />
</div> </div>
......
const codes = {
// 0x0 range - arithmetic ops
// name, baseCost, off stack, on stack, dynamic
0x00: ['STOP', 0, 0, 0, false],
0x01: ['ADD', 3, 2, 1, false],
0x02: ['MUL', 5, 2, 1, false],
0x03: ['SUB', 3, 2, 1, false],
0x04: ['DIV', 5, 2, 1, false],
0x05: ['SDIV', 5, 2, 1, false],
0x06: ['MOD', 5, 2, 1, false],
0x07: ['SMOD', 5, 2, 1, false],
0x08: ['ADDMOD', 8, 3, 1, false],
0x09: ['MULMOD', 8, 3, 1, false],
0x0a: ['EXP', 10, 2, 1, false],
0x0b: ['SIGNEXTEND', 5, 1, 1, false],
// 0x10 range - bit ops
0x10: ['LT', 3, 2, 1, false],
0x11: ['GT', 3, 2, 1, false],
0x12: ['SLT', 3, 2, 1, false],
0x13: ['SGT', 3, 2, 1, false],
0x14: ['EQ', 3, 2, 1, false],
0x15: ['ISZERO', 3, 1, 1, false],
0x16: ['AND', 3, 2, 1, false],
0x17: ['OR', 3, 2, 1, false],
0x18: ['XOR', 3, 2, 1, false],
0x19: ['NOT', 3, 1, 1, false],
0x1a: ['BYTE', 3, 2, 1, false],
// 0x20 range - crypto
0x20: ['SHA3', 30, 2, 1, false],
// 0x30 range - closure state
0x30: ['ADDRESS', 2, 0, 1, true],
0x31: ['BALANCE', 20, 1, 1, true],
0x32: ['ORIGIN', 2, 0, 1, true],
0x33: ['CALLER', 2, 0, 1, true],
0x34: ['CALLVALUE', 2, 0, 1, true],
0x35: ['CALLDATALOAD', 3, 1, 1, true],
0x36: ['CALLDATASIZE', 2, 0, 1, true],
0x37: ['CALLDATACOPY', 3, 3, 0, true],
0x38: ['CODESIZE', 2, 0, 1, false],
0x39: ['CODECOPY', 3, 3, 0, false],
0x3a: ['GASPRICE', 2, 0, 1, false],
0x3b: ['EXTCODESIZE', 20, 1, 1, true],
0x3c: ['EXTCODECOPY', 20, 4, 0, true],
// '0x40' range - block operations
0x40: ['BLOCKHASH', 20, 1, 1, true],
0x41: ['COINBASE', 2, 0, 1, true],
0x42: ['TIMESTAMP', 2, 0, 1, true],
0x43: ['NUMBER', 2, 0, 1, true],
0x44: ['DIFFICULTY', 2, 0, 1, true],
0x45: ['GASLIMIT', 2, 0, 1, true],
// 0x50 range - 'storage' and execution
0x50: ['POP', 2, 1, 0, false],
0x51: ['MLOAD', 3, 1, 1, false],
0x52: ['MSTORE', 3, 2, 0, false],
0x53: ['MSTORE8', 3, 2, 0, false],
0x54: ['SLOAD', 50, 1, 1, true],
0x55: ['SSTORE', 0, 2, 0, true],
0x56: ['JUMP', 8, 1, 0, false],
0x57: ['JUMPI', 10, 2, 0, false],
0x58: ['PC', 2, 0, 1, false],
0x59: ['MSIZE', 2, 0, 1, false],
0x5a: ['GAS', 2, 0, 1, false],
0x5b: ['JUMPDEST', 1, 0, 0, false],
// 0x60, range
0x60: ['PUSH', 3, 0, 1, false],
0x61: ['PUSH', 3, 0, 1, false],
0x62: ['PUSH', 3, 0, 1, false],
0x63: ['PUSH', 3, 0, 1, false],
0x64: ['PUSH', 3, 0, 1, false],
0x65: ['PUSH', 3, 0, 1, false],
0x66: ['PUSH', 3, 0, 1, false],
0x67: ['PUSH', 3, 0, 1, false],
0x68: ['PUSH', 3, 0, 1, false],
0x69: ['PUSH', 3, 0, 1, false],
0x6a: ['PUSH', 3, 0, 1, false],
0x6b: ['PUSH', 3, 0, 1, false],
0x6c: ['PUSH', 3, 0, 1, false],
0x6d: ['PUSH', 3, 0, 1, false],
0x6e: ['PUSH', 3, 0, 1, false],
0x6f: ['PUSH', 3, 0, 1, false],
0x70: ['PUSH', 3, 0, 1, false],
0x71: ['PUSH', 3, 0, 1, false],
0x72: ['PUSH', 3, 0, 1, false],
0x73: ['PUSH', 3, 0, 1, false],
0x74: ['PUSH', 3, 0, 1, false],
0x75: ['PUSH', 3, 0, 1, false],
0x76: ['PUSH', 3, 0, 1, false],
0x77: ['PUSH', 3, 0, 1, false],
0x78: ['PUSH', 3, 0, 1, false],
0x79: ['PUSH', 3, 0, 1, false],
0x7a: ['PUSH', 3, 0, 1, false],
0x7b: ['PUSH', 3, 0, 1, false],
0x7c: ['PUSH', 3, 0, 1, false],
0x7d: ['PUSH', 3, 0, 1, false],
0x7e: ['PUSH', 3, 0, 1, false],
0x7f: ['PUSH', 3, 0, 1, false],
0x80: ['DUP', 3, 0, 1, false],
0x81: ['DUP', 3, 0, 1, false],
0x82: ['DUP', 3, 0, 1, false],
0x83: ['DUP', 3, 0, 1, false],
0x84: ['DUP', 3, 0, 1, false],
0x85: ['DUP', 3, 0, 1, false],
0x86: ['DUP', 3, 0, 1, false],
0x87: ['DUP', 3, 0, 1, false],
0x88: ['DUP', 3, 0, 1, false],
0x89: ['DUP', 3, 0, 1, false],
0x8a: ['DUP', 3, 0, 1, false],
0x8b: ['DUP', 3, 0, 1, false],
0x8c: ['DUP', 3, 0, 1, false],
0x8d: ['DUP', 3, 0, 1, false],
0x8e: ['DUP', 3, 0, 1, false],
0x8f: ['DUP', 3, 0, 1, false],
0x90: ['SWAP', 3, 0, 0, false],
0x91: ['SWAP', 3, 0, 0, false],
0x92: ['SWAP', 3, 0, 0, false],
0x93: ['SWAP', 3, 0, 0, false],
0x94: ['SWAP', 3, 0, 0, false],
0x95: ['SWAP', 3, 0, 0, false],
0x96: ['SWAP', 3, 0, 0, false],
0x97: ['SWAP', 3, 0, 0, false],
0x98: ['SWAP', 3, 0, 0, false],
0x99: ['SWAP', 3, 0, 0, false],
0x9a: ['SWAP', 3, 0, 0, false],
0x9b: ['SWAP', 3, 0, 0, false],
0x9c: ['SWAP', 3, 0, 0, false],
0x9d: ['SWAP', 3, 0, 0, false],
0x9e: ['SWAP', 3, 0, 0, false],
0x9f: ['SWAP', 3, 0, 0, false],
0xa0: ['LOG', 375, 2, 0, false],
0xa1: ['LOG', 375, 3, 0, false],
0xa2: ['LOG', 375, 4, 0, false],
0xa3: ['LOG', 375, 5, 0, false],
0xa4: ['LOG', 375, 6, 0, false],
// '0xf0' range - closures
0xf0: ['CREATE', 32000, 3, 1, true],
0xf1: ['CALL', 40, 7, 1, true],
0xf2: ['CALLCODE', 40, 7, 1, true],
0xf3: ['RETURN', 0, 2, 0, false],
0xf4: ['DELEGATECALL', 40, 6, 1, true],
// '0x70', range - other
0xff: ['SUICIDE', 0, 1, 0, false]
}
module.exports = function (op, full) {
var code = codes[op] ? codes[op] : ['INVALID', 0]
var opcode = code[0]
if (full) {
if (opcode === 'LOG') {
opcode += op - 0xa0
}
if (opcode === 'PUSH') {
opcode += op - 0x5f
}
if (opcode === 'DUP') {
opcode += op - 0x7f
}
if (opcode === 'SWAP') {
opcode += op - 0x8f
}
}
return {name: opcode, fee: code[1], in: code[2], out: code[3], dynamic: code[4], async: code[5]}
}
\ No newline at end of file
...@@ -26,7 +26,6 @@ module.exports = React.createClass({ ...@@ -26,7 +26,6 @@ module.exports = React.createClass({
render: function() { render: function() {
return ( return (
<div style={style.container} > <div style={style.container} >
<div><h3>Transaction details</h3></div>
<input onChange={this.updateBlockN} type="text" placeholder= {"Block number e.g. : " + this.state.blockNumber}></input> <input onChange={this.updateBlockN} type="text" placeholder= {"Block number e.g. : " + this.state.blockNumber}></input>
<input onChange={this.updateTxN} type="text" placeholder={"Transaction Number e.g. : " + this.state.txNumber}></input> <input onChange={this.updateTxN} type="text" placeholder={"Transaction Number e.g. : " + this.state.txNumber}></input>
<button onClick={this.submit}>Get</button> <button onClick={this.submit}>Get</button>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment