openethereum/js/packages/dapp-chaindeploy/store.js

715 lines
19 KiB
JavaScript
Raw Normal View History

// Copyright 2015-2017 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/>.
import { action, computed, observable } from 'mobx';
import { contracts as contractsInfo, registry as registryInfo } from './contracts';
import { apps } from './dapps';
import { api } from './parity';
import { executeContract, isValidNumber, validateCode } from './utils';
export default class ContractsStore {
@observable apps = null;
@observable badges = null;
@observable contracts = null;
@observable error = null;
@observable registry = null;
constructor () {
this.apps = apps;
this.badges = contractsInfo.filter((contract) => contract.isBadge);
this.contracts = contractsInfo.filter((contract) => !contract.isBadge);
this.registry = registryInfo;
api.subscribe('eth_blockNumber', this.onNewBlockNumber);
}
@computed get contractBadgereg () {
return this.contracts.find((contract) => contract.id === 'badgereg');
}
@computed get contractDappreg () {
return this.contracts.find((contract) => contract.id === 'dappreg');
}
@computed get contractGithubhint () {
return this.contracts.find((contract) => contract.id === 'githubhint');
}
@computed get contractTokenreg () {
return this.contracts.find((contract) => contract.id === 'tokenreg');
}
@computed get isBadgeDeploying () {
return this.badges
.filter((contract) => contract.isDeploying)
.length !== 0;
}
@computed get isContractDeploying () {
return this.contracts
.filter((contract) => contract.isDeploying)
.length !== 0;
}
@computed get isDappDeploying () {
return this.apps
.filter((app) => app.isDeploying)
.length !== 0;
}
@computed get haveAllBadges () {
return this.badges
.filter((contract) => !contract.instance || !contract.hasLatestCode || !contract.badgeImageHash || !contract.badgeImageMatch || !contract.isBadgeRegistered)
.length === 0;
}
@computed get haveAllContracts () {
return this.contracts
.filter((contract) => !contract.instance || !contract.hasLatestCode)
.length === 0;
}
@computed get haveAllDapps () {
return this.apps
.filter((app) => {
return !app.isOnChain ||
!app.imageHash || !app.imageMatch ||
(app.source.contentHash && !app.contentMatch) ||
(app.source.manifestHash && !app.manifestMatch);
})
.length === 0;
}
@action refreshApps = () => {
this.apps = [].concat(this.apps.peek());
}
@action refreshContracts = () => {
this.badges = [].concat(this.badges.peek());
this.contracts = [].concat(this.contracts.peek());
}
@action setError = (error) => {
console.error(error);
this.error = error.message
? error.message
: error;
}
@action setRegistryAddress = (address, isOnChain = false) => {
if (this.registry.address !== address || !this.registry.instance) {
console.log(`registry found at ${address}`);
this.registry = Object.assign({}, this.registry, {
address,
instance: api.newContract(this.registry.abi, address).instance,
isOnChain
});
}
}
@action setRegistryCode (byteCode) {
this.registry.hasLatestCode = validateCode(this.registry.byteCode, byteCode);
}
@action setRegistryDeploying = (isDeploying = false) => {
this.registry = Object.assign({}, this.registry, {
isDeploying,
status: isDeploying
? 'Deploying contract'
: null
});
}
@action setBadgeId = (badge, badgeId) => {
badge.badgeId = badgeId;
badge.isBadgeRegistered = true;
this.refreshContracts();
}
@action setBadgeImageHash = (badge, imageHash) => {
badge.badgeImageHash = imageHash;
badge.badgeImageMatch = badge.badgeSource.imageHash === imageHash;
this.refreshContracts();
}
@action setContractAddress = (contract, address, isOnChain = false) => {
if (contract.address !== address || !contract.instance || contract.isOnChain !== isOnChain) {
console.log(`${contract.id} found at ${address}`);
contract.address = address;
contract.instance = api.newContract(contract.abi, address).instance;
contract.isOnChain = isOnChain;
this.refreshContracts();
}
}
@action setContractCode (contract, byteCode) {
contract.hasLatestCode = validateCode(contract.byteCode, byteCode);
this.refreshContracts();
}
@action setContractDeploying = (contract, isDeploying = false) => {
contract.isDeploying = isDeploying;
contract.status = isDeploying
? 'Deploying contract'
: null;
this.refreshContracts();
}
@action setContractStatus = (contract, status) => {
contract.status = status;
this.refreshContracts();
}
@action setAppDeploying = (app, isDeploying = false) => {
app.isDeploying = isDeploying;
app.status = isDeploying
? 'Registering app'
: null;
this.refreshApps();
}
@action setAppFound = (app, isOnChain = false) => {
if (app.isOnChain !== isOnChain) {
console.log(`${app.name} found on dappreg`);
app.isOnChain = isOnChain;
this.refreshApps();
}
}
@action setAppContentHash = (app, contentHash) => {
if (app.contentHash !== contentHash) {
console.log(`${app.name} has contentHash ${contentHash}`);
app.contentHash = contentHash;
app.contentMatch = contentHash === app.source.contentHash;
this.refreshApps();
}
}
@action setAppImageHash = (app, imageHash) => {
if (app.imageHash !== imageHash) {
console.log(`${app.name} has imageHash ${imageHash}`);
app.imageHash = imageHash;
app.imageMatch = imageHash === app.source.imageHash;
this.refreshApps();
}
}
@action setAppManifestHash = (app, manifestHash) => {
if (app.manifestHash !== manifestHash) {
console.log(`${app.name} has manifestHash ${manifestHash}`);
app.manifestHash = manifestHash;
app.manifestMatch = manifestHash === app.source.manifestHash;
this.refreshApps();
}
}
@action setAppStatus = (app, status) => {
console.log(app.id, status);
app.status = status;
this.refreshApps();
}
deployApp = (app) => {
console.log(`Registering application ${app.id}`);
this.setAppDeploying(app, true);
const options = {};
const values = [app.hashId];
return api.parity
.defaultAccount()
.then((defaultAccount) => {
options.from = defaultAccount;
if (app.isOnChain) {
return true;
}
return this.contractDappreg.instance
.fee.call({}, [])
.then((fee) => {
options.value = fee;
return executeContract(app.id, this.contractDappreg, 'register', options, values);
});
})
.then(() => {
if (app.imageHash && app.imageMatch) {
return true;
}
this.setAppStatus(app, 'Registering image url');
return this
.registerHash(app.source.imageHash, app.source.imageUrl, options.from)
.then(() => this.setAppMeta(app, 'IMG', app.source.imageHash, options.from));
})
.then(() => {
if (!app.source.manifestHash || app.manifestMatch) {
return true;
}
this.setAppStatus(app, 'Registering manifest url');
return this
.registerHash(app.source.manifestHash, app.source.manifestUrl, options.from)
.then(() => this.setAppMeta(app, 'MANIFEST', app.source.manifestHash, options.from));
})
.then(() => {
if (!app.source.contentHash || app.contentMatch) {
return true;
}
this.setAppStatus(app, 'Registering content url');
return this
.registerRepo(app.source.contentHash, app.source.contentUrl, options.from)
.then(() => this.setAppMeta(app, 'CONTENT', app.source.contentHash, options.from));
})
.catch(() => {
return null;
})
.then(() => {
this.setAppDeploying(app, false);
});
}
deployApps = () => {
this.apps
.filter((app) => {
return !app.isDeploying &&
(
!app.isOnChain ||
(!app.imageHash || !app.imageMatch) ||
(app.source.contentHash && !app.contentMatch) ||
(app.source.manifestHash && !app.manifestMatch)
);
})
.forEach(this.deployApp);
}
_deployContract = (contract) => {
console.log(`Deploying contract ${contract.id}`);
const options = {
data: contract.byteCode
};
return api.parity
.defaultAccount()
.then((defaultAccount) => {
options.from = defaultAccount;
return api
.newContract(contract.abi)
.deploy(options, contract.deployParams, (error, data) => {
if (error) {
console.error(contract.id, error);
} else {
console.log(contract.id, data);
}
})
.then((contractAddress) => {
return [contractAddress, defaultAccount];
});
});
}
deployContract = (contract) => {
if (contract.hasLatestCode) {
return Promise.resolve(false);
}
let defaultAccount = '0x0';
this.setContractDeploying(contract, true);
return this
._deployContract(contract)
.then(([address, _defaultAccount]) => {
const isOnChain = contract.isOnChain;
defaultAccount = _defaultAccount;
this.setContractAddress(contract, address);
return isOnChain
? true
: this.reserveAddress(contract, defaultAccount);
})
.then(() => {
return this.registerAddress(contract, defaultAccount);
})
.catch(() => {
return null;
})
.then(() => {
this.setContractDeploying(contract, false);
});
}
deployBadge = (badge) => {
let defaultAccount;
return this
.deployContract(badge)
.then(() => {
this.setContractDeploying(badge, true);
return api.parity.defaultAccount();
})
.then((_defaultAccount) => {
defaultAccount = _defaultAccount;
if (badge.isBadgeRegistered) {
return true;
}
this.setContractStatus(badge, 'Registering with badgereg');
return this.registerBadge(badge, defaultAccount);
})
.then(() => {
if (badge.badgeImageMatch) {
return true;
}
this.setContractStatus(badge, 'Registering image url');
return this
.registerHash(badge.badgeSource.imageHash, badge.badgeSource.imageUrl, defaultAccount)
.then(() => this.registerBadgeImage(badge, badge.badgeSource.imageHash, defaultAccount));
})
.then(() => {
this.setContractDeploying(badge, false);
});
}
deployContracts = () => {
this.contracts
.filter((contract) => !contract.isDeploying && (!contract.instance || !contract.hasLatestCode))
.forEach(this.deployContract);
}
deployBadges = () => {
this.badges
.filter((contract) => !contract.isDeploying && (!contract.instance || !contract.hasLatestCode || !contract.badgeImageHash || !contract.badgeImageMatch || !contract.isBadgeRegistered))
.forEach(this.deployBadge);
}
deployRegistry = () => {
this.setRegistryDeploying(true);
return this
._deployContract(this.registry)
.then(([address]) => {
this.setRegistryDeploying(false);
this.setRegistryAddress(address);
});
}
registerBadge = (badge, fromAddress) => {
const options = {
from: fromAddress
};
const values = [badge.address, api.util.sha3.text(badge.id.toLowerCase())];
return this.contractBadgereg.instance
.fee.call({}, [])
.then((fee) => {
options.value = fee;
return executeContract(badge.id, this.contractBadgereg, 'register', options, values);
});
}
registerBadgeImage = (badge, hash, fromAddress) => {
const options = {
from: fromAddress
};
const values = [badge.badgeId, 'IMG', hash];
this.setContractStatus(badge, 'Setting meta IMG');
return executeContract(badge.id, this.contractBadgereg, 'setMeta', options, values);
}
setAppMeta = (app, key, meta, fromAddress) => {
const options = {
from: fromAddress
};
const values = [app.hashId, key, meta];
this.setAppStatus(app, `Setting meta ${key}`);
return executeContract(app.id, this.contractDappreg, 'setMeta', options, values);
}
reserveAddress = (contract, fromAddress) => {
const options = { from: fromAddress };
const values = [api.util.sha3.text(contract.id.toLowerCase())];
this.setContractStatus(contract, 'Reserving name');
return this.registry.instance
.fee.call({}, [])
.then((value) => {
options.value = value;
return executeContract(contract.id, this.registry, 'reserve', options, values);
});
}
registerAddress = (contract, fromAddress) => {
const options = { from: fromAddress };
const values = [api.util.sha3.text(contract.id.toLowerCase()), 'A', contract.address];
this.setContractStatus(contract, 'Setting lookup address');
return executeContract(contract.id, this.registry, 'setAddress', options, values);
}
registerRepo = (hash, content, fromAddress) => {
const options = {
from: fromAddress
};
const values = [hash, content.repo || content, content.commit || 0];
return this.contractGithubhint.instance
.entries.call({}, [hash])
.then(([imageUrl, commit, owner]) => {
if (isValidNumber(owner)) {
return true;
}
return executeContract(hash, this.contractGithubhint, 'hint', options, values);
})
.catch(() => false);
}
registerHash = (hash, url, fromAddress) => {
const options = {
from: fromAddress
};
const values = [hash, url];
return this.contractGithubhint.instance
.entries.call({}, [hash])
.then(([imageUrl, commit, owner]) => {
if (isValidNumber(owner)) {
return true;
}
return executeContract(hash, this.contractGithubhint, 'hintURL', options, values);
})
.catch(() => false);
}
findRegistry = () => {
if (this.registry.address && this.registry.hasLatestCode) {
return Promise.resolve(this.registry);
}
return api.parity
.registryAddress()
.then((address) => {
if (isValidNumber(address)) {
this.setRegistryAddress(address, true);
}
return api.eth.getCode(address);
})
.then((byteCode) => {
this.setRegistryCode(byteCode);
});
}
findApps = () => {
if (!this.contractDappreg.instance) {
return Promise.resolve(false);
}
return Promise
.all(
this.apps.map((app) => {
return app.isOnChain
? Promise.resolve([[0]])
: this.contractDappreg.instance.get.call({}, [app.hashId]);
})
)
.then((apps) => {
apps.forEach(([_id, owner], index) => {
const id = api.util.bytesToHex(_id);
if (isValidNumber(id)) {
this.setAppFound(this.apps[index], true);
}
});
return Promise.all(
this.apps.map((app) => {
return !app.isOnChain || (app.imageHash && app.imageMatch)
? Promise.resolve([[0], [0], [0]])
: Promise.all([
this.contractDappreg.instance.meta.call({}, [app.hashId, 'CONTENT']),
this.contractDappreg.instance.meta.call({}, [app.hashId, 'IMG']),
this.contractDappreg.instance.meta.call({}, [app.hashId, 'MANIFEST'])
]);
})
);
})
.then((hashes) => {
hashes.forEach(([content, image, manifest], index) => {
const contentHash = api.util.bytesToHex(content);
const imageHash = api.util.bytesToHex(image);
const manifestHash = api.util.bytesToHex(manifest);
if (isValidNumber(contentHash)) {
this.setAppContentHash(this.apps[index], contentHash);
}
if (isValidNumber(imageHash)) {
this.setAppImageHash(this.apps[index], imageHash);
}
if (isValidNumber(manifestHash)) {
this.setAppManifestHash(this.apps[index], manifestHash);
}
});
});
}
findBadges = () => {
if (!this.contractBadgereg.instance) {
return Promise.resolve(false);
}
return this
.findContracts(this.badges)
.then(() => {
return Promise.all(
this.badges.map((badge) => {
return badge.isBadgeRegistered
? Promise.resolve([0, 0, 0])
: this.contractBadgereg.instance.fromAddress.call({}, [badge.address]);
})
);
})
.then((badgeInfos) => {
badgeInfos.forEach(([id, name, owner], index) => {
if (isValidNumber(owner)) {
this.setBadgeId(this.badges[index], id);
}
});
return Promise
.all(
this.badges.map((badge) => {
return !badge.isBadgeRegistered
? Promise.resolve([0])
: this.contractBadgereg.instance.meta.call({}, [badge.badgeId, 'IMG']);
})
);
})
.then((images) => {
images.forEach((imageBytes, index) => {
const imageHash = api.util.bytesToHex(imageBytes);
if (isValidNumber(imageHash)) {
this.setBadgeImageHash(this.badges[index], imageHash);
}
});
});
}
findContracts = (contracts = this.contracts) => {
if (!this.registry.instance) {
return Promise.resolve(false);
}
return Promise
.all(
contracts.map((contract) => {
const hashId = api.util.sha3.text(contract.id.toLowerCase());
return contract.isOnChain
? Promise.resolve([0, 0])
: Promise.all([
this.registry.instance.getAddress.call({}, [hashId, 'A']),
this.registry.instance.getOwner.call({}, [hashId])
]);
})
)
.then((addresses) => {
addresses.forEach(([address, owner], index) => {
if (isValidNumber(owner) && isValidNumber(address)) {
this.setContractAddress(contracts[index], address, true);
}
});
return Promise.all(
contracts.map((contract) => {
return !contract.address || contract.hasLatestCode
? Promise.resolve(null)
: api.eth.getCode(contract.address);
})
);
})
.then((codes) => {
codes.forEach((byteCode, index) => {
if (byteCode) {
this.setContractCode(contracts[index], byteCode);
}
});
});
}
onNewBlockNumber = (error, blockNumber) => {
if (error) {
return;
}
return this
.findRegistry()
.then(this.findContracts)
.then(this.findApps)
.then(this.findBadges)
.catch(this.setError);
}
}