Skip to content
This repository has been archived by the owner on Jan 24, 2022. It is now read-only.

Commit

Permalink
Initial implementation of simple project
Browse files Browse the repository at this point in the history
- Add methods to proxy to support deploying and managing
  • Loading branch information
spalladino committed Sep 8, 2018
1 parent ff0f023 commit 48d57cf
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 22 deletions.
62 changes: 62 additions & 0 deletions packages/lib/src/project/SimpleProject.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import _ from 'lodash';
import { buildCallData, callDescription } from '../utils/ABIs';
import { deploy, sendDataTransaction } from "../utils/Transactions";
import Proxy from '../proxy/Proxy';
import Logger from '../utils/Logger';

const log = new Logger('SimpleProject')

export default class SimpleProject {
constructor(name = 'main', txParamsAdmin = {}, txParamsInitializer = {}) {
this.txParamsAdmin = txParamsAdmin
this.txParamsInitializer = txParamsInitializer
this.name = name
}

async createProxy(contractClass, { initMethod, initArgs } = {}) {
if (!_.isEmpty(initArgs) && !initMethod) initMethod = 'initialize'

const implementation = await this._deployImplementation(contractClass);
log.info(`Creating proxy to logic contract ${implementation.address}`)
const proxy = await Proxy.deploy(implementation, this.txParamsAdmin)
await this._tryInitializeProxy(proxy, contractClass, initMethod, initArgs)

log.info(`Instance created at ${proxy.address}`)
return new contractClass(proxy.address);
}

async upgradeProxy(proxyAddress, contractClass, { initMethod, initArgs } = {}) {
const implementation = await this._deployImplementation(contractClass);
log.info(`Upgrading proxy to new logic contract at ${implementation.address}`)
const proxy = Proxy.at(proxyAddress, this.txParamsAdmin)
await proxy.upgradeTo(implementation)
await this._tryInitializeProxy(proxy, contractClass, initMethod, initArgs)

log.info(`Instance at ${proxyAddress} upgraded`)
return new contractClass(proxyAddress);
}

async changeProxyAdmin(proxyAddress, newAdmin) {
const proxy = Proxy.at(proxyAddress, this.txParamsAdmin)
await proxy.changeAdmin(newAdmin)
log.info(`Proxy admin changed to ${newAdmin}`)
return proxy
}

async _deployImplementation(contractClass) {
log.info(`Deploying logic contract for ${contractClass.contractName}`);
const implementation = await deploy(contractClass, [], this.txParamsAdmin);
return implementation;
}

async _tryInitializeProxy(proxy, contractClass, initMethodName, initArgs) {
if (!initMethodName) return;
if (this.txParamsInitializer.from === this.txParamsAdmin.from) {
throw Error(`Cannot initialize the proxy from the same address as its admin address. Make sure you use a different 'from' account for the initialization transaction params.`)
}

const { method: initMethod, callData } = buildCallData(contractClass, initMethodName, initArgs);
log.info(`Initializing proxy at ${proxy.address} by calling ${callDescription(initMethod, initArgs)}`);
await sendDataTransaction(proxy.contract, Object.assign({}, this.txParamsInitializer, { data: callData }))
}
}
40 changes: 32 additions & 8 deletions packages/lib/src/proxy/Proxy.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,34 @@
import { promisify } from 'util'
import { Contracts } from '../utils/Contracts';
import Contracts from '../utils/Contracts';
import { toAddress } from '../utils/Addresses';
import { deploy as deployContract, sendTransaction } from "../utils/Transactions";

export default class Proxy {
static at(address) {
return new Proxy(address)
static at(address, txParams = {}) {
const Proxy = Contracts.getFromLib('AdminUpgradeabilityProxy')
const contract = new Proxy(address)
return new this(contract, txParams)
}

static async create(implementation, txParams = {}) {
const proxyContract = await deployContract(Contracts.getFromLib('AdminUpgradeabilityProxy'), [toAddress(implementation)], txParams)
return new Proxy(toAddress(proxyContract))
static async deploy(implementation, txParams = {}) {
const contract = await deployContract(Contracts.getFromLib('AdminUpgradeabilityProxy'), [toAddress(implementation)], txParams)
return new this(contract, txParams)
}

constructor(address) {
this.address = address
constructor(contract, txParams = {}) {
this.contract = contract
this.address = toAddress(contract)
this.txParams = txParams
}

async upgradeTo(address) {
await this._checkAdmin()
return sendTransaction(this.contract.upgradeTo, [toAddress(address)], this.txParams)
}

async changeAdmin(newAdmin) {
await this._checkAdmin()
return sendTransaction(this.contract.changeAdmin, [newAdmin], this.txParams)
}

async implementation() {
Expand All @@ -29,4 +44,13 @@ export default class Proxy {
async getStorageAt(position) {
return promisify(web3.eth.getStorageAt.bind(web3.eth))(this.address, position)
}

async _checkAdmin() {
const currentAdmin = await this.admin()
const from = this.txParams.from
// TODO: If no `from` is set, load which is the default account and use it to compare against the current admin
if (from && currentAdmin !== from) {
throw new Error(`Cannot modify proxy from non-admin account: current admin is ${currentAdmin} and sender is ${from}`)
}
}
}
2 changes: 0 additions & 2 deletions packages/lib/src/utils/Transactions.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,8 @@ export async function sendDataTransaction(contract, txParams) {
if (txParams.gas) {
return contract.sendTransaction(txParams)
}

// Estimate gas for the call
const estimatedGas = await estimateGas({ to: contract.address, ... txParams });

// Run the tx
const gasToUse = await calculateActualGas(estimatedGas);
return contract.sendTransaction({ gas: gasToUse, ... txParams });
Expand Down
1 change: 1 addition & 0 deletions packages/lib/test/src/project/AppProject.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ contract('AppProject', function (accounts) {
});

shouldManageProxies({
supportsNames: true,
otherAdmin: another,
setImplementations: async function () {
await this.project.setImplementation(ImplV1, "DummyImplementation")
Expand Down
28 changes: 16 additions & 12 deletions packages/lib/test/src/project/ProxyProject.behaviour.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ const Impl = Contracts.getFromLocal('Impl');
const DummyImplementation = Contracts.getFromLocal('DummyImplementation');
const DummyImplementationV2 = Contracts.getFromLocal('DummyImplementationV2');

export default function shouldManageProxies({ otherAdmin, setImplementations }) {
export default function shouldManageProxies({ otherAdmin, setImplementations, supportsNames }) {

describe('like proxy managing project', function () {
describe('like a proxy managing project', function () {
describe('createProxy', function () {
beforeEach('setting implementations', setImplementations);

Expand All @@ -21,11 +21,13 @@ export default function shouldManageProxies({ otherAdmin, setImplementations })
await assertIsProxy(instance, this.adminAddress);
})

it('creates a proxy given contract name', async function () {
const instance = await this.project.createProxy(Impl, { contractName: 'DummyImplementation' });
await assertIsVersion(instance, 'V1');
await assertIsProxy(instance, this.adminAddress);
})
if (supportsNames) {
it('creates a proxy given contract name', async function () {
const instance = await this.project.createProxy(Impl, { contractName: 'DummyImplementation' });
await assertIsVersion(instance, 'V1');
await assertIsProxy(instance, this.adminAddress);
})
}

it('creates and initializes a proxy', async function () {
const instance = await this.project.createProxy(DummyImplementation, { initArgs: [10] });
Expand All @@ -45,11 +47,13 @@ export default function shouldManageProxies({ otherAdmin, setImplementations })
await assertIsProxy(upgraded, this.adminAddress);
})

it('upgrades a proxy given contract name', async function () {
const upgraded = await this.project.upgradeProxy(this.instance.address, Impl, { contractName: 'DummyImplementationV2' });
await assertIsVersion(upgraded, 'V2');
await assertIsProxy(upgraded, this.adminAddress);
})
if (supportsNames) {
it('upgrades a proxy given contract name', async function () {
const upgraded = await this.project.upgradeProxy(this.instance.address, Impl, { contractName: 'DummyImplementationV2' });
await assertIsVersion(upgraded, 'V2');
await assertIsProxy(upgraded, this.adminAddress);
})
}

it('upgrades and migrates a proxy', async function () {
const upgraded = await this.project.upgradeProxy(this.instance.address, DummyImplementationV2, { initMethod: 'migrate', initArgs: [20] });
Expand Down
22 changes: 22 additions & 0 deletions packages/lib/test/src/project/SimpleProject.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict'
require('../../setup')

import SimpleProject from '../../../src/project/SimpleProject'
import shouldManageProxies from './ProxyProject.behaviour';
import { noop } from 'lodash';

contract('SimpleProject', function (accounts) {
const [_, owner, another, initializer] = accounts
const name = 'MyProject'

beforeEach('initializing', async function () {
this.project = new SimpleProject(name, { from: owner }, { from: initializer })
this.adminAddress = owner
});

shouldManageProxies({
supportsNames: false,
otherAdmin: another,
setImplementations: noop
})
})

0 comments on commit 48d57cf

Please sign in to comment.