Only Runtime Contracts (#225)

This commit is contained in:
Daniel Ternyak 2018-11-25 22:02:35 -06:00 committed by GitHub
parent 8a97142d82
commit 00219e65c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 146 additions and 98 deletions

View File

@ -4,7 +4,7 @@ matrix:
- language: node_js
node_js: 8.13.0
before_install:
- cd frontend/
- cd frontend
install: yarn
script:
- yarn run lint
@ -16,8 +16,7 @@ matrix:
- cd backend/
- cp .env.example .env
env:
- FLASK_APP=app.py FLASK_DEBUG=1 CROWD_FUND_URL=https://eip-712.herokuapp.com/contract/crowd-fund
CROWD_FUND_FACTORY_URL=https://eip-712.herokuapp.com/contract/factory
- CROWD_FUND_URL=https://eip-712.herokuapp.com/contract/crowd-fund CROWD_FUND_FACTORY_URL=https://eip-712.herokuapp.com/contract/factory
install: pip install -r requirements/dev.txt
script:
- flask test

View File

@ -6,12 +6,18 @@ DATABASE_URL="sqlite:////tmp/dev.db"
REDISTOGO_URL="redis://localhost:6379"
SECRET_KEY="not-so-secret"
SENDGRID_API_KEY="optional, but emails won't send without it"
# for ropsten use the following
# ETHEREUM_ENDPOINT_URI = "https://ropsten.infura.io/API_KEY"
ETHEREUM_ENDPOINT_URI = "http://localhost:8545"
CROWD_FUND_URL = "https://eip-712.herokuapp.com/contract/crowd-fund"
CROWD_FUND_FACTORY_URL = "https://eip-712.herokuapp.com/contract/factory"
# CROWD_FUND_URL = "https://eip-712.herokuapp.com/contract/crowd-fund"
# CROWD_FUND_FACTORY_URL = "https://eip-712.herokuapp.com/contract/factory"
CROWD_FUND_URL = "http://localhost:5000/dev-contracts/CrowdFund.json"
CROWD_FUND_FACTORY_URL = "http://localhost:5000/dev-contracts/CrowdFundFactory.json"
# SENTRY_DSN="https://PUBLICKEY@sentry.io/PROJECTID"
# SENTRY_RELEASE="optional, overrides git hash"
UPLOAD_DIRECTORY = "/tmp"
UPLOAD_URL = "http://localhost:5000" # for constructing download url

View File

@ -5,7 +5,7 @@ from flask_cors import CORS
from sentry_sdk.integrations.flask import FlaskIntegration
import sentry_sdk
from grant import commands, proposal, user, comment, milestone, admin, email
from grant import commands, proposal, user, comment, milestone, admin, email, web3 as web3module
from grant.extensions import bcrypt, migrate, db, ma, mail, web3
from grant.settings import SENTRY_RELEASE, ENV
@ -46,6 +46,9 @@ def register_blueprints(app):
app.register_blueprint(milestone.views.blueprint)
app.register_blueprint(admin.views.blueprint)
app.register_blueprint(email.views.blueprint)
# Only add these routes locally
if ENV == 'development':
app.register_blueprint(web3module.dev_contracts.blueprint)
def register_shellcontext(app):

View File

@ -19,6 +19,7 @@ from .models import(
proposal_contribution_schema,
db
)
import traceback
blueprint = Blueprint("proposal", __name__, url_prefix="/api/v1/proposals")
@ -88,12 +89,16 @@ def get_proposals(stage):
else:
proposals = Proposal.query.order_by(Proposal.date_created.desc()).all()
dumped_proposals = proposals_schema.dump(proposals)
for p in dumped_proposals:
proposal_contract = read_proposal(p['proposal_address'])
p['crowd_fund'] = proposal_contract
filtered_proposals = list(filter(lambda p: p['crowd_fund'] is not None, dumped_proposals))
return filtered_proposals
try:
for p in dumped_proposals:
proposal_contract = read_proposal(p['proposal_address'])
p['crowd_fund'] = proposal_contract
filtered_proposals = list(filter(lambda p: p['crowd_fund'] is not None, dumped_proposals))
return filtered_proposals
except Exception as e:
print(e)
print(traceback.format_exc())
return {"message": "Oops! Something went wrong."}, 500
@blueprint.route("/", methods=["POST"])
@requires_sm

View File

@ -9,7 +9,11 @@ environment variables.
import subprocess
from environs import Env
git_revision_short_hash = subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD'])
def git_revision_short_hash():
try:
return subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD'])
except subprocess.CalledProcessError:
return 0
env = Env()
env.read_env()
@ -33,7 +37,7 @@ SENDGRID_DEFAULT_FROM = "noreply@grant.io"
ETHEREUM_PROVIDER = "http"
ETHEREUM_ENDPOINT_URI = env.str("ETHEREUM_ENDPOINT_URI")
SENTRY_DSN = env.str("SENTRY_DSN", default=None)
SENTRY_RELEASE = env.str("SENTRY_RELEASE", default=git_revision_short_hash)
SENTRY_RELEASE = env.str("SENTRY_RELEASE", default=git_revision_short_hash())
UPLOAD_DIRECTORY = env.str("UPLOAD_DIRECTORY")
UPLOAD_URL = env.str("UPLOAD_URL")
MAX_CONTENT_LENGTH = 5 * 1024 * 1024 # 5MB (limits file uploads, raises RequestEntityTooLarge)

View File

@ -0,0 +1 @@
from . import dev_contracts

View File

@ -0,0 +1,19 @@
import json
from flask import Blueprint, jsonify
blueprint = Blueprint('dev-contracts', __name__, url_prefix='/dev-contracts')
@blueprint.route("/CrowdFundFactory.json", methods=["GET"])
def factory():
with open("../contract/build/contracts/CrowdFundFactory.json", "r") as read_file:
crowd_fund_factory_json = json.load(read_file)
return jsonify(crowd_fund_factory_json)
@blueprint.route("/CrowdFund.json", methods=["GET"])
def crowd_find():
with open("../contract/build/contracts/CrowdFund.json", "r") as read_file:
crowd_fund_json = json.load(read_file)
return jsonify(crowd_fund_json)

View File

@ -1,10 +1,10 @@
import json
import time
from flask_web3 import current_web3
from .util import batch_call, call_array, RpcError
import requests
from grant.settings import CROWD_FUND_URL
import requests
from flask_web3 import current_web3
from grant.settings import CROWD_FUND_URL
from .util import batch_call, call_array, RpcError
crowd_fund_abi = None
@ -14,19 +14,14 @@ def get_crowd_fund_abi():
if crowd_fund_abi:
return crowd_fund_abi
if CROWD_FUND_URL:
crowd_fund_json = requests.get(CROWD_FUND_URL).json()
crowd_fund_abi = crowd_fund_json['abi']
return crowd_fund_abi
with open("../contract/build/contracts/CrowdFund.json", "r") as read_file:
crowd_fund_abi = json.load(read_file)['abi']
return crowd_fund_abi
crowd_fund_json = requests.get(CROWD_FUND_URL).json()
crowd_fund_abi = crowd_fund_json['abi']
return crowd_fund_abi
def read_proposal(address):
current_web3.eth.defaultAccount = current_web3.eth.accounts[0]
current_web3.eth.defaultAccount = '0x537680D921C000fC52Af9962ceEb4e359C50F424' if not current_web3.eth.accounts else \
current_web3.eth.accounts[0]
crowd_fund_abi = get_crowd_fund_abi()
contract = current_web3.eth.contract(address=address, abi=crowd_fund_abi)
@ -106,6 +101,7 @@ def read_proposal(address):
def get_no_vote(i):
return derived_results['getContributorMilestoneVote' + contrib_address + str(i)]
no_votes = list(map(get_no_vote, range(len(crowd_fund['milestones']))))
contrib = {

View File

@ -1,14 +1,14 @@
import json
import time
import eth_tester.backends.pyevm.main as py_evm_main
import requests
from flask_web3 import current_web3
from grant.extensions import web3
from grant.settings import CROWD_FUND_URL, CROWD_FUND_FACTORY_URL
from grant.web3.proposal import read_proposal
from ..config import BaseTestConfig
import requests
# increase gas limit on eth-tester
# https://github.com/ethereum/web3.py/issues/1013
# https://gitter.im/ethereum/py-evm?at=5b7eb68c4be56c5918854337
@ -24,17 +24,8 @@ class TestWeb3ProposalRead(BaseTestConfig):
BaseTestConfig.setUp(self)
# the following will properly configure web3 with test config
web3.init_app(self.real_app)
if CROWD_FUND_FACTORY_URL:
crowd_fund_factory_json = requests.get(CROWD_FUND_FACTORY_URL).json()
else:
with open("../frontend/client/lib/contracts/CrowdFundFactory.json", "r") as read_file:
crowd_fund_factory_json = json.load(read_file)
if CROWD_FUND_URL:
self.crowd_fund_json = requests.get(CROWD_FUND_URL).json()
else:
with open("../frontend/client/lib/contracts/CrowdFund.json", "r") as read_file:
self.crowd_fund_json = json.load(read_file)
crowd_fund_factory_json = requests.get(CROWD_FUND_FACTORY_URL).json()
self.crowd_fund_json = requests.get(CROWD_FUND_URL).json()
current_web3.eth.defaultAccount = current_web3.eth.accounts[0]
CrowdFundFactory = current_web3.eth.contract(
abi=crowd_fund_factory_json['abi'], bytecode=crowd_fund_factory_json['bytecode'])

1
contract/.nvmrc Normal file
View File

@ -0,0 +1 @@
8.13.0

View File

@ -4,14 +4,19 @@ FUND_ETH_ADDRESSES=0x4bbeEB066eD09B7AEd07bF39EEe0460DFa261520,0xDECAF9CD2367cdbb
# Disable typescript checking for dev building (reduce build time & resource usage)
NO_DEV_TS_CHECK=true
NODE_ENV=development
# Set the public host url (no trailing slash)
PUBLIC_HOST_URL=https://demo.grant.io
BACKEND_URL=http://localhost:5000
# sentry
SENTRY_DSN="https://PUBLICKEY@sentry.io/PROJECTID"
SENTRY_DSN=https://PUBLICKEY@sentry.io/PROJECTID
SENTRY_RELEASE="optional, overrides git hash"
CROWD_FUND_URL = "https://eip-712.herokuapp.com/contract/crowd-fund"
CROWD_FUND_FACTORY_URL = "https://eip-712.herokuapp.com/contract/factory"
# CROWD_FUND_URL=https://eip-712.herokuapp.com/contract/crowd-fund
# CROWD_FUND_FACTORY_URL=https://eip-712.herokuapp.com/contract/factory
CROWD_FUND_URL=http://localhost:5000/dev-contracts/CrowdFund.json
CROWD_FUND_FACTORY_URL=http://localhost:5000/dev-contracts/CrowdFundFactory.json

View File

@ -12,16 +12,30 @@ require('../config/env');
module.exports = {};
const CHECK_CONTRACT_IDS = ['CrowdFundFactory.json']
const clean = (module.exports.clean = () => {
rimraf.sync(paths.contractsBuild);
});
const compile = (module.exports.compile = () => {
childProcess.execSync('yarn build', { cwd: paths.contractsBase });
logMessage('truffle compile, please wait...', 'info');
try {
childProcess.execSync('yarn build', { cwd: paths.contractsBase });
} catch (e) {
logMessage(e.stdout.toString('utf8'), 'error');
process.exit(1);
}
});
const migrate = (module.exports.migrate = () => {
childProcess.execSync('truffle migrate', { cwd: paths.contractsBase });
logMessage('truffle migrate, please wait...', 'info');
try {
childProcess.execSync('truffle migrate', { cwd: paths.contractsBase });
} catch (e) {
logMessage(e.stdout.toString('utf8'), 'error');
process.exit(1);
}
});
const makeWeb3Conn = () => {
@ -62,20 +76,41 @@ const getGanacheNetworkId = (module.exports.getGanacheNetworkId = () => {
.catch(() => -1);
});
const checkContractsNetworkIds = (module.exports.checkContractsNetworkIds = id =>
const checkContractsNetworkIds = (module.exports.checkContractsNetworkIds = (
id,
retry = false,
) =>
new Promise((res, rej) => {
const buildDir = paths.contractsBuild;
fs.readdir(buildDir, (err, names) => {
fs.readdir(buildDir, (err) => {
if (err) {
logMessage(`No contracts build directory @ ${buildDir}`, 'error');
res(false);
} else {
const allHaveId = names.reduce((ok, name) => {
const allHaveId = CHECK_CONTRACT_IDS.reduce((ok, name) => {
const contract = require(path.join(buildDir, name));
if (Object.keys(contract.networks).length > 0 && !contract.networks[id]) {
const actual = Object.keys(contract.networks).join(', ');
logMessage(`${name} should have networks[${id}], it has ${actual}`, 'error');
return false;
const contractHasKeys = Object.keys(contract.networks).length > 0;
if (!contractHasKeys) {
if (retry) {
logMessage(
'Contract does not contain network keys after retry. Exiting. Please manually debug Contract JSON.',
'error',
);
process.exit(1);
} else {
logMessage('Contract does not contain any keys. Will migrate.');
migrate();
return checkContractsNetworkIds(id, true);
}
} else {
if (contractHasKeys && !contract.networks[id]) {
const actual = Object.keys(contract.networks).join(', ');
logMessage(
`${name} should have networks[${id}], it has ${actual}`,
'error',
);
return false;
}
}
return true && ok;
}, true);
@ -128,9 +163,7 @@ module.exports.ethereumCheck = () =>
if (!allHaveId) {
logMessage('Contract problems, will compile & migrate.', 'warning');
clean();
logMessage('truffle compile, please wait...', 'info');
compile();
logMessage('truffle migrate, please wait...', 'info');
migrate();
fundWeb3v1();
} else {

View File

@ -10,11 +10,7 @@ export async function getCrowdFundContract(web3: Web3 | null, deployedAddress: s
}
if (!contractCache[deployedAddress]) {
let CrowdFund;
if (process.env.CROWD_FUND_FACTORY_URL) {
CrowdFund = await fetchCrowdFundJSON();
} else {
CrowdFund = await import('./contracts/CrowdFund.json');
}
CrowdFund = await fetchCrowdFundJSON();
try {
contractCache[deployedAddress] = await getContractInstance(
web3,

View File

@ -10,7 +10,11 @@ const getContractInstance = async (
// get network ID and the deployed address
const networkId = await web3.eth.net.getId();
if (!deployedAddress && !contractDefinition.networks[networkId]) {
throw new WrongNetworkError('Wrong web3 network configured');
throw new WrongNetworkError(
`Wrong web3 network configured. Deployed address: ${deployedAddress}; networkId: ${networkId}, contractDefinitionNetworks: ${JSON.stringify(
contractDefinition.networks,
)}`,
);
}
deployedAddress = deployedAddress || contractDefinition.networks[networkId].address;

View File

@ -6,18 +6,12 @@ import { safeEnable } from 'utils/web3';
import types from './types';
import { fetchCrowdFundFactoryJSON } from 'api/api';
/* tslint:disable no-var-requires --- TODO: find a better way to import contract */
let CrowdFundFactory = require('lib/contracts/CrowdFundFactory.json');
export function* bootstrapWeb3(): SagaIterator {
// Don't attempt to bootstrap web3 on SSR
if (process.env.SERVER_SIDE_RENDER) {
return;
}
if (process.env.CROWD_FUND_FACTORY_URL) {
CrowdFundFactory = yield call(fetchCrowdFundFactoryJSON);
}
const CrowdFundFactory = yield call(fetchCrowdFundFactoryJSON);
yield put<any>(setWeb3());
yield take(types.WEB3_FULFILLED);

View File

@ -2,6 +2,8 @@ const fs = require('fs');
const path = require('path');
const paths = require('./paths');
const childProcess = require('child_process');
const dotenv = require('dotenv');
const { logMessage } = require('../bin/utils');
delete require.cache[require.resolve('./paths')];
@ -11,21 +13,17 @@ if (!process.env.NODE_ENV) {
);
}
// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
const dotenvFiles = [
`${paths.dotenv}.${process.env.NODE_ENV}.local`,
`${paths.dotenv}.${process.env.NODE_ENV}`,
process.env.NODE_ENV !== 'test' && `${paths.dotenv}.local`,
paths.dotenv,
].filter(Boolean);
dotenvFiles.forEach(dotenvFile => {
if (fs.existsSync(dotenvFile)) {
require('dotenv').config({
path: dotenvFile,
});
// Override local ENV variables with .env
if (fs.existsSync(paths.dotenv)) {
const envConfig = dotenv.parse(fs.readFileSync(paths.dotenv));
// tslint:disable-next-line
for (const k in envConfig) {
if (process.env[k]) {
logMessage(`Warning! Over-writing existing ENV Variable ${k}`);
}
process.env[k] = envConfig[k];
}
});
}
const envProductionRequiredHandler = (envVariable, fallbackValue) => {
if (!process.env[envVariable]) {
@ -72,6 +70,8 @@ process.env.NODE_PATH = (process.env.NODE_PATH || '')
module.exports = () => {
const raw = {
BACKEND_URL: process.env.BACKEND_URL,
CROWD_FUND_FACTORY_URL: process.env.CROWD_FUND_FACTORY_URL,
CROWD_FUND_URL: process.env.CROWD_FUND_URL,
NODE_ENV: process.env.NODE_ENV || 'development',
PORT: process.env.PORT || 3000,
PUBLIC_HOST_URL: process.env.PUBLIC_HOST_URL,

View File

@ -1,6 +1,6 @@
const ModuleDependencyWarning = require('webpack/lib/ModuleDependencyWarning');
// supress unfortunate warnings due to transpileOnly=true and certain ts export patterns
// suppress unfortunate warnings due to transpileOnly=true and certain ts export patterns
// https://github.com/TypeStrong/ts-loader/issues/653#issuecomment-390889335
// https://github.com/TypeStrong/ts-loader/issues/751

View File

@ -4,10 +4,9 @@
"main": "index.js",
"license": "MIT",
"scripts": {
"analyze": "NODE_ENV=production ANALYZE=true next build ./client",
"build": "cross-env NODE_ENV=production node bin/build.js",
"dev": "rm -rf ./node_modules/.cache && cross-env NODE_ENV=development BACKEND_URL=http://localhost:5000 node bin/dev.js",
"lint": "tslint --project ./tsconfig.json --config ./tslint.json -e \"**/build/**\"",
"build": "node bin/build.js",
"dev": "rm -rf ./node_modules/.cache && node bin/dev.js",
"lint": "tslint --project ./tsconfig.json --config ./tslint.json -e \"**/build/**\" -e \"**/bin/**\"",
"start": "NODE_ENV=production node ./build/server/server.js",
"now": "npm run build && now -e BACKEND_URL=https://grant-stage.herokuapp.com",
"heroku-postbuild": "yarn build",
@ -90,7 +89,6 @@
"copy-webpack-plugin": "^4.6.0",
"core-js": "^2.5.7",
"cors": "^2.8.4",
"cross-env": "^5.2.0",
"css-loader": "^1.0.0",
"dotenv": "^6.0.0",
"ethereum-blockies-base64": "1.0.2",

View File

@ -4838,13 +4838,6 @@ cropperjs@v1.0.0-rc.3:
version "1.0.0-rc.3"
resolved "https://registry.yarnpkg.com/cropperjs/-/cropperjs-1.0.0-rc.3.tgz#50a7c7611befc442702f845ede77d7df4572e82b"
cross-env@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-5.2.0.tgz#6ecd4c015d5773e614039ee529076669b9d126f2"
dependencies:
cross-spawn "^6.0.5"
is-windows "^1.0.0"
cross-spawn@5.1.0, cross-spawn@^5.0.1:
version "5.1.0"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
@ -8459,7 +8452,7 @@ is-utf8@^0.2.0:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
is-windows@^1.0.0, is-windows@^1.0.1, is-windows@^1.0.2:
is-windows@^1.0.1, is-windows@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"