openethereum/js/src/views/WriteContract/writeContractStore.js

524 lines
13 KiB
JavaScript
Raw Normal View History

2016-12-11 19:31:31 +01:00
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
2016-12-20 01:55:57 +01:00
import { action, observable, transaction } from 'mobx';
import store from 'store';
import { debounce } from 'lodash';
import { sha3 } from '~/api/util/sha3';
2016-12-20 01:55:57 +01:00
import SolidityUtils from '~/util/solidity';
const WRITE_CONTRACT_STORE_KEY = '_parity::writeContractStore';
const SNIPPETS = {
snippet0: {
name: 'Token.sol',
description: 'Standard ERP20 Token Contract',
2016-11-25 19:32:58 +01:00
id: 'snippet0', sourcecode: require('raw-loader!../../contracts/snippets/token.sol')
},
snippet1: {
name: 'StandardToken.sol',
description: 'Implementation of ERP20 Token Contract',
2016-11-25 19:32:58 +01:00
id: 'snippet1', sourcecode: require('raw-loader!../../contracts/snippets/standard-token.sol')
},
snippet2: {
name: 'HumanStandardToken.sol',
description: 'Implementation of the Human Token Contract',
2016-11-25 19:32:58 +01:00
id: 'snippet2', sourcecode: require('raw-loader!../../contracts/snippets/human-standard-token.sol')
},
snippet3: {
name: 'Wallet.sol',
description: 'Implementation of a multisig Wallet',
id: 'snippet3', sourcecode: require('raw-loader!../../contracts/snippets/wallet.sol')
}
};
let instance = null;
export default class WriteContractStore {
@observable sourcecode = '';
@observable compiled = false;
@observable compiling = false;
@observable loading = true;
@observable contractIndex = -1;
@observable contract = null;
@observable contracts = {};
@observable errors = [];
@observable annotations = [];
@observable builds = [];
@observable selectedBuild = -1;
2016-12-20 02:11:04 +01:00
@observable autocompile = false;
@observable optimize = false;
@observable showDeployModal = false;
@observable showSaveModal = false;
@observable showLoadModal = false;
@observable savedContracts = {};
@observable selectedContract = {};
@observable workerError = null;
2016-12-19 14:39:10 +01:00
loadingSolidity = false;
lastCompilation = {};
snippets = SNIPPETS;
worker = null;
2016-12-20 01:55:57 +01:00
useWorker = true;
solc = {};
constructor () {
this.debouncedCompile = debounce(this.handleCompile, 1000);
}
static get () {
if (!instance) {
instance = new WriteContractStore();
}
return instance;
}
@action setWorkerError (error) {
this.workerError = error;
}
@action setEditor (editor) {
this.editor = editor;
}
@action setWorker (worker) {
this.worker = worker;
this
.fetchSolidityVersions()
.then(() => this.reloadContracts());
}
fetchSolidityVersions () {
return fetch('https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/bin/list.json')
.then((r) => r.json())
.then((data) => {
const { builds, releases, latestRelease } = data;
let latestIndex = -1;
let promise = Promise.resolve();
this.builds = builds.reverse().map((build, index) => {
if (releases[build.version] === build.path) {
build.release = true;
if (build.version === latestRelease) {
build.latest = true;
promise = promise.then(() => this.loadSolidityVersion(build));
latestIndex = index;
}
}
return build;
});
this.selectedBuild = latestIndex;
return promise;
2016-12-20 01:55:57 +01:00
})
.catch((error) => {
this.setWorkerError(error);
});
}
@action handleImport = (sourcecode) => {
this.reloadContracts(-1, sourcecode);
}
@action handleSelectBuild = (_, index, value) => {
this.selectedBuild = value;
return this.loadSolidityVersion(this.builds[value]);
}
2016-12-20 01:55:57 +01:00
getCompiler (build) {
const { longVersion } = build;
if (!this.solc[longVersion]) {
this.solc[longVersion] = SolidityUtils
.getCompiler(build)
.then((compiler) => {
this.solc[longVersion] = compiler;
return compiler;
})
.catch((error) => {
this.setWorkerError(error);
});
}
return Promise.resolve(this.solc[longVersion]);
}
@action loadSolidityVersion = (build) => {
2016-12-20 01:55:57 +01:00
if (this.worker === undefined) {
return;
2016-12-20 01:55:57 +01:00
} else if (this.worker === null) {
this.useWorker = false;
}
2016-12-19 14:39:10 +01:00
if (this.loadingSolidity) {
return this.loadingSolidity;
}
2016-12-20 01:55:57 +01:00
if (this.useWorker) {
this.loadingSolidity = this.worker
.postMessage({
action: 'load',
data: build
})
.then((result) => {
if (result !== 'ok') {
throw new Error('error while loading solidity: ' + result);
}
this.loadingSolidity = false;
this.loading = false;
})
.catch((error) => {
console.warn('error while loading solidity', error);
this.useWorker = false;
this.loadingSolidity = null;
return this.loadSolidityVersion(build);
});
} else {
this.loadingSolidity = this
.getCompiler(build)
.then(() => {
this.loadingSolidity = false;
this.loading = false;
return 'ok';
})
.catch((error) => {
this.setWorkerError(error);
this.loadingSolidity = false;
this.loading = false;
});
}
2016-12-19 14:39:10 +01:00
return this.loadingSolidity;
}
@action handleOpenDeployModal = () => {
this.showDeployModal = true;
}
@action handleCloseDeployModal = () => {
this.showDeployModal = false;
}
@action handleOpenLoadModal = () => {
this.showLoadModal = true;
}
@action handleCloseLoadModal = () => {
this.showLoadModal = false;
}
@action handleOpenSaveModal = () => {
this.showSaveModal = true;
}
@action handleCloseSaveModal = () => {
this.showSaveModal = false;
}
@action handleSelectContract = (_, index, value) => {
this.contractIndex = value;
this.contract = this.contracts[Object.keys(this.contracts)[value]];
}
2016-12-20 01:55:57 +01:00
compile = (data) => {
if (this.useWorker) {
return this.worker.postMessage({
action: 'compile',
data
});
}
return new Promise((resolve, reject) => {
window.setTimeout(() => {
this
.getCompiler(data.build)
.then((compiler) => {
return SolidityUtils.compile(data, compiler);
})
.then(resolve)
.catch(reject);
}, 0);
});
}
@action handleCompile = (loadFiles = false) => {
2016-12-20 01:55:57 +01:00
transaction(() => {
this.compiled = false;
this.compiling = true;
});
const build = this.builds[this.selectedBuild];
const version = build.longVersion;
const sourcecode = this.sourcecode.replace(/\n+/g, '\n').replace(/\s(\s+)/g, ' ');
2016-12-20 02:11:04 +01:00
const hash = sha3(JSON.stringify({ version, sourcecode, optimize: this.optimize }));
let promise = Promise.resolve(null);
if (hash === this.lastCompilation.hash) {
promise = new Promise((resolve) => {
window.setTimeout(() => {
resolve(this.lastCompilation);
}, 500);
});
2016-12-20 01:55:57 +01:00
} else {
promise = loadFiles && this.useWorker
? this.sendFilesToWorker()
: Promise.resolve();
promise = promise
.then(() => {
2016-12-20 01:55:57 +01:00
return this.compile({
sourcecode: sourcecode,
2016-12-20 02:11:04 +01:00
build: build,
optimize: this.optimize
});
})
.then((data) => {
const result = this.parseCompiled(data);
this.lastCompilation = {
result: result,
date: new Date(),
version: data.version,
hash
};
return this.lastCompilation;
})
.catch((error) => {
this.setWorkerError(error);
});
}
2016-12-19 14:39:10 +01:00
return promise.then((data = null) => {
if (data) {
const {
contract, contractIndex,
annotations, contracts, errors
} = data.result;
2016-12-19 14:39:10 +01:00
this.contract = contract;
this.contractIndex = contractIndex;
2016-12-19 14:39:10 +01:00
this.annotations = annotations;
this.contracts = contracts;
this.errors = errors;
}
this.compiled = true;
this.compiling = false;
});
}
2016-12-20 02:11:04 +01:00
@action handleAutocompileToggle = () => {
this.autocompile = !this.autocompile;
}
@action handleOptimizeToggle = () => {
this.optimize = !this.optimize;
}
@action parseCompiled = (data) => {
const { contracts } = data;
const { errors = [] } = data;
const errorAnnotations = this.parseErrors(errors);
const formalAnnotations = this.parseErrors(data.formal && data.formal.errors, true);
const annotations = [].concat(
errorAnnotations,
formalAnnotations
);
const contractKeys = Object.keys(contracts || {});
const contract = contractKeys.length ? contracts[contractKeys[0]] : null;
const contractIndex = contractKeys.length ? 0 : -1;
return {
contract, contractIndex,
contracts, errors, annotations
};
}
parseErrors = (data, formal = false) => {
const regex = /^(.*):(\d+):(\d+):\s*([a-z]+):\s*((.|[\r\n])+)$/i;
return (data || [])
.filter((e) => regex.test(e))
.map((error, index) => {
const match = regex.exec(error);
const contract = match[1];
const row = parseInt(match[2]) - 1;
const column = parseInt(match[3]);
const type = formal ? 'warning' : match[4].toLowerCase();
const text = match[5];
return {
contract,
row, column,
type, text,
formal
};
});
}
@action handleEditSourcecode = (value, compile = false) => {
this.sourcecode = value;
const localStore = store.get(WRITE_CONTRACT_STORE_KEY) || {};
store.set(WRITE_CONTRACT_STORE_KEY, {
...localStore,
current: value
});
if (compile) {
this.handleCompile();
2016-12-20 02:11:04 +01:00
} else if (this.autocompile) {
this.debouncedCompile();
}
}
@action handleSaveContract = () => {
if (this.selectedContract && this.selectedContract.id !== undefined) {
return this.handleSaveNewContract({
...this.selectedContract,
sourcecode: this.sourcecode
});
}
return this.handleOpenSaveModal();
}
getId (contracts) {
return Object.values(contracts)
.map((c) => c.id)
.reduce((max, id) => Math.max(max, id), 0) + 1;
}
@action handleSaveNewContract = (data) => {
const { name, sourcecode, id } = data;
const localStore = store.get(WRITE_CONTRACT_STORE_KEY) || {};
const savedContracts = localStore.saved || {};
const cId = (id !== undefined)
? id
: this.getId(savedContracts);
store.set(WRITE_CONTRACT_STORE_KEY, {
...localStore,
saved: {
...savedContracts,
[ cId ]: { sourcecode, id: cId, name, timestamp: Date.now() }
}
});
this.reloadContracts(cId);
}
@action reloadContracts = (id, sourcecode) => {
const localStore = store.get(WRITE_CONTRACT_STORE_KEY) || {};
this.savedContracts = localStore.saved || {};
const cId = id !== undefined ? id : localStore.currentId;
this.selectedContract = this.savedContracts[cId] || {};
this.sourcecode = sourcecode !== undefined
? sourcecode
: this.selectedContract.sourcecode || localStore.current || '';
store.set(WRITE_CONTRACT_STORE_KEY, {
...localStore,
currentId: this.selectedContract ? cId : null,
current: this.sourcecode
});
this.resizeEditor();
// Send the new files to the Worker and compile
return this.handleCompile(true);
}
@action handleLoadContract = (contract) => {
const { sourcecode, id } = contract;
this.reloadContracts(id, sourcecode);
}
@action handleDeleteContract = (id) => {
const localStore = store.get(WRITE_CONTRACT_STORE_KEY) || {};
const savedContracts = Object.assign({}, localStore.saved || {});
if (savedContracts[id]) {
delete savedContracts[id];
}
store.set(WRITE_CONTRACT_STORE_KEY, {
...localStore,
saved: savedContracts
});
this.reloadContracts();
}
@action handleNewContract = () => {
this.reloadContracts(-1, '');
}
@action resizeEditor = () => {
try {
this.editor.refs.brace.editor.resize();
} catch (e) {}
}
sendFilesToWorker = () => {
2016-12-20 01:55:57 +01:00
if (!this.useWorker) {
return Promise.resolve();
}
const files = [].concat(
Object.values(this.snippets),
Object.values(this.savedContracts)
);
return this.worker.postMessage({
action: 'setFiles',
data: files
});
}
}