├── .eslintrc.json ├── .gitignore ├── LICENSE ├── README.md ├── create-proxy.js ├── package-lock.json ├── package.json ├── postinstall.js ├── summarize-proxy.js ├── templates ├── migration │ └── deploy_contracts.js ├── solidity │ ├── LibraryLock.sol │ ├── LibraryLockDataLayout.sol │ ├── Migrations.sol │ ├── Owned.sol │ ├── Proxiable.sol │ ├── Proxy.sol │ ├── Sample.sol │ └── SampleDataLayout.sol └── test │ └── sample.js ├── test-proxy.js └── truffle-plugin.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "node": true 5 | }, 6 | "parserOptions": { 7 | "ecmaVersion": 9, 8 | "sourceType": "module" 9 | }, 10 | "rules": { 11 | "no-unused-vars": [ 12 | "error", 13 | { "argsIgnorePattern": "^_", "varsIgnorePattern": "debug" } 14 | ], 15 | "quote-props": [ "error", "consistent" ], 16 | "semi": [ "error", "always" ], 17 | "no-mixed-operators": "warn", 18 | "no-tabs": "warn", 19 | "no-useless-escape": "warn" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Michael Coon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Welcome to truffle-proxy 2 | 3 | Winning project at **ConsenSys Grants Hackathon - New York - July 2019** 4 | 5 | > See **truffle-proxy-ui** [here](https://github.com/mdcoon/truffle-proxy-ui) 6 | 7 | > Extend Truffle CLI with [EIP-1822](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1822.md) proxy support. Proxy support provides a simple upgrade path for contracts to maintain storage, while making changes to the underlying contract implementation. 8 | 9 | ## Install 10 | 11 | ```sh 12 | // tip: to start a new truffle project: 13 | // truffle init 14 | // within your truffle project: 15 | npm init -y 16 | npm install --save-dev git+https://github.com/mdcoon/truffle-proxy 17 | ``` 18 | 19 | Add the following to your `truffle-config.js` 20 | ```json 21 | plugins: [ 22 | "truffle-proxy" 23 | ] 24 | ``` 25 | 26 | 27 | ## Usage 28 | 29 | ### Add proxy support to a truffle project 30 | Generates EIP-1820 compatible proxy implementation along with example files and 31 | unit tests for your project. 32 | ```sh 33 | truffle run create-proxy 34 | ``` 35 | 36 | ### Run unit tests 37 | Executes all unit tests against an embedded Ganache blockchain compatible with 38 | proxy contract. 39 | ```sh 40 | truffle run test-proxy 41 | ``` 42 | 43 | ### Display summary network info 44 | Displays a summary of network / address information for deployed contracts. 45 | Includes details highlighting proxy contracts. 46 | ```sh 47 | truffle run summarize-proxy 48 | ``` 49 | 50 | ## Authors 51 | 52 | 👤 **Mike Coon** 53 | 54 | - Github: [@mdcoon](https://github.com/mdcoon) 55 | 56 | 👤 **Harvinder Ghotra** 57 | 58 | - Github: [@hghotra](https://github.com/hghotra) 59 | 60 | 👤 **Mitchell Opatowsky** 61 | 62 | - Github: [@official-mitchell](https://github.com/official-mitchell) 63 | 64 | 👤 **Mike Powers** 65 | 66 | - Github: [@mjpowersjr](https://github.com/mjpowersjr) 67 | 68 | 👤 **Will Shahda** 69 | 70 | - Github: [@opz](https://github.com/opz) 71 | 72 | 73 | 74 | ## Show your support 75 | 76 | Give a ⭐️ if this project helped you! 77 | -------------------------------------------------------------------------------- /create-proxy.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const logger = console;; 4 | 5 | const PLUGIN_NAME = 'truffle-proxy'; 6 | const TEMPLATE_DIR_NAME = 'templates'; 7 | const CONTRACT_DIR_NAME = 'contracts'; 8 | const MIGRATION_DIR_NAME = 'migrations'; 9 | const TEST_DIR_NAME = 'test'; 10 | 11 | const USAGE = ``; 12 | 13 | const SUMMARY = ` 14 | Congratulations, you have just created a proxy contract! 15 | 16 | To learn more about how to interact with a proxy contract, view Sample.sol 17 | `; 18 | 19 | 20 | module.exports = async (config) => { 21 | const commandName = config._[0]; 22 | const commandArguments = config._.slice(1); 23 | 24 | if (commandArguments && commandArguments[0] == 'help') { 25 | logger.info(USAGE); 26 | return; 27 | } 28 | 29 | const projectDir = config._values.working_directory; 30 | const nodeModuleDir = path.join(projectDir, 'node_modules'); 31 | const pluginDir = path.join(nodeModuleDir, PLUGIN_NAME); 32 | const templateDir = path.join(pluginDir, TEMPLATE_DIR_NAME) 33 | 34 | const solidityTemplateDir = path.join(templateDir, 'solidity'); 35 | const migrationTemplateDir = path.join(templateDir, 'migration'); 36 | const testTemplateDir = path.join(templateDir, 'test'); 37 | 38 | const contractDestination = path.join(projectDir, CONTRACT_DIR_NAME); 39 | const migrationDestination = path.join(projectDir, MIGRATION_DIR_NAME); 40 | const testDestination = path.join(projectDir, TEST_DIR_NAME); 41 | 42 | if (fs.existsSync(solidityTemplateDir) && 43 | fs.existsSync(migrationTemplateDir) && 44 | fs.existsSync(contractDestination) && 45 | fs.existsSync(migrationDestination) && 46 | fs.existsSync(testDestination) 47 | ) { 48 | 49 | solidityTemplates = fs.readdirSync(solidityTemplateDir); 50 | migrationTemplates = fs.readdirSync(migrationTemplateDir); 51 | tests = fs.readdirSync(testTemplateDir); 52 | 53 | migrations = fs.readdirSync(migrationDestination); 54 | 55 | logger.info(`Generating solidity templates...`); 56 | solidityTemplates.forEach((template) => { 57 | const source = path.join(solidityTemplateDir, template); 58 | const destination = path.join(contractDestination, template); 59 | logger.info(`...${template}...`); 60 | fs.copyFileSync(source, destination); 61 | }); 62 | 63 | var migrationCounter = 0; 64 | migrations.forEach((migration) => { 65 | if (migration.split('_')[0] > migrationCounter) { 66 | migrationCounter = migration.split('_')[0]; 67 | } 68 | }); 69 | 70 | logger.info(`Generating migration templates...`); 71 | migrationTemplates.forEach((template) => { 72 | migrationCounter++; 73 | destTemplate = migrationCounter + "_" + template; 74 | const source = path.join(migrationTemplateDir, template); 75 | const destination = path.join(migrationDestination, destTemplate); 76 | logger.info(`...${destTemplate}...`); 77 | fs.copyFileSync(source, destination); 78 | }); 79 | 80 | logger.info(`Generating unit tests...`); 81 | tests.forEach((test) => { 82 | const source = path.join(testTemplateDir, test); 83 | const destination = path.join(testDestination, test); 84 | logger.info(`...${test}...`); 85 | fs.copyFileSync(source, destination); 86 | }); 87 | 88 | logger.info(SUMMARY); 89 | } else { 90 | logger.error("Something went wrong .."); 91 | // FIXME: something went wrong 92 | } 93 | 94 | }; 95 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "truffle-proxy", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "truffle-proxy", 3 | "version": "1.0.0", 4 | "description": "Upgradeable contract support in Truffle", 5 | "main": "create-proxy.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "postinstall": "node ./postinstall.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/mdcoon/truffle-proxy.git" 13 | }, 14 | "author": "", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/mdcoon/truffle-proxy/issues" 18 | }, 19 | "homepage": "https://github.com/mdcoon/truffle-proxy#readme", 20 | "dependencies": { 21 | "ganache": "^6.4.4", 22 | "truffle-core": "^5.0.28", 23 | "truffle-environment": "^0.1.4", 24 | "truffle-legacy-system": "^1.0.7", 25 | "truffle-migrate": "^3.0.26", 26 | "truffle-resolver": "^5.0.14", 27 | "truffle-workflow-compile": "^2.0.24", 28 | "web3": "^1.0.0-beta.55" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /postinstall.js: -------------------------------------------------------------------------------- 1 | 2 | const metadata = require('./package.json'); 3 | 4 | const appRoot = process.env.PWD; 5 | 6 | console.log("PWD: " + appRoot); 7 | 8 | 9 | console.log(`Please add ${metadata.name} to the 'plugins' section of your truffle config!`); 10 | -------------------------------------------------------------------------------- /summarize-proxy.js: -------------------------------------------------------------------------------- 1 | // an alternative parse-json: https://www.npmjs.com/package/parse-json (may delete this) 2 | // JSON parse MDN reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#Using_the_reviver_parameter 3 | // Written out structure: https://www.tutorialkart.com/nodejs/nodejs-parse-json/ 4 | // Written out alternative structure & explanation: https://flaviocopes.com/nodejs-parse-json/ 5 | // String Methods: levelup.gitconnected.com/essential-javascript-string-methods-f1841dad1961 6 | 7 | // 1. require node modules 8 | var fs = require("fs"); // include file system module 9 | var path = require("path"); 10 | 11 | // 2. Source the data 12 | // here the problem is that I need to source the data more precisely 13 | // ConfigOption could be a solution. 14 | // Access the Artifact 15 | 16 | // 4. set conditions for JSON Parse 17 | // Logic for isolating the network ID 18 | // Logic for isolating the address 19 | // Logic for isolating the transaction hash 20 | 21 | // take from command line flags 22 | 23 | // "networks": 1525343635906" 24 | // { 25 | // "address": "0xd8e2af5be9af2a45fc3ee7cdcb68d9bcc37a3c81", 26 | // "transactionHash": "0xb8ec575a9f3eca4a11a3f61170231a1816f7c68940d8487e56567adcf5c0a21e" 27 | // } 28 | 29 | // 5. parseState method 30 | function parseState(dataSource) { 31 | 32 | let results = []; 33 | 34 | let fileContents = null; 35 | try { 36 | fileContents = fs.readFileSync( 37 | dataSource, // "/path/to/file.json" 38 | "utf-8" 39 | ); 40 | } catch(err) { 41 | console.error(err); 42 | return; 43 | } 44 | 45 | let jsonParsed = null; 46 | try { 47 | jsonParsed = JSON.parse(fileContents); 48 | } catch(err) { 49 | console.error(err); 50 | return; 51 | } 52 | 53 | const contractName = jsonParsed.contractName; 54 | const networkIds = Object.keys(jsonParsed.networks); 55 | 56 | networkIds.forEach((networkId) => { 57 | const deployment = jsonParsed.networks[networkId]; 58 | const address = deployment.address; 59 | 60 | const result = { 61 | contractName: contractName, 62 | networkId: networkId, 63 | address: address 64 | }; 65 | 66 | results.push(result); 67 | }); 68 | 69 | 70 | return results; 71 | } 72 | 73 | 74 | const USAGE = ``; 75 | 76 | const SUMMARY = ` 77 | Congratulations, you have just created a proxy contract! 78 | 79 | To learn more about how to integrate with a proxy contract, view Example.sol 80 | `; 81 | 82 | async function summarize (config) { 83 | // const commandName = config._[0]; 84 | // const commandArguments = config._.slice(1); 85 | 86 | const contractBuildDir = path.join(config._values.working_directory, 'build', 'contracts'); 87 | 88 | const filenames = fs.readdirSync(contractBuildDir); 89 | 90 | let results = []; 91 | filenames.forEach((filename) => { 92 | const filePath = path.join(contractBuildDir, filename); 93 | const r = parseState(filePath); 94 | results = results.concat(r); 95 | }); 96 | 97 | console.log(results); 98 | 99 | }; 100 | 101 | module.exports = summarize; 102 | 103 | summarize({ 104 | _values: { 105 | working_directory: "/home/mike/projects/MetaCoin" 106 | } 107 | }); 108 | -------------------------------------------------------------------------------- /templates/migration/deploy_contracts.js: -------------------------------------------------------------------------------- 1 | const Proxy = artifacts.require("Proxy"); 2 | const Sample = artifacts.require("Sample") 3 | 4 | module.exports = async function(deployer) { 5 | 6 | await deployer.deploy(Sample); 7 | await Sample.deployed().then(async d=>{ 8 | let con = new web3.eth.Contract(d.abi, d.address, {address: d.address}) 9 | let accts = await web3.eth.getAccounts(); 10 | let p = null; 11 | try { 12 | p = await Proxy.deployed(); 13 | } catch (e) { 14 | //ignore 15 | } 16 | 17 | if(!p) { 18 | let txnData = con.methods.postConstructor(5).encodeABI(); 19 | console.log("Deploying new proxy instance"); 20 | await deployer.deploy(Proxy, txnData, d.address); 21 | await Proxy.deployed().then(async p2=>{ 22 | let gbSig = con.methods.visit().encodeABI(); 23 | console.log("Visit call", gbSig, 'from', accts[0], 'to', p2.address); 24 | await web3.eth.sendTransaction({ 25 | from: accts[0], 26 | to: p2.address, 27 | gas: 600000, 28 | gasPrice: 100000000, 29 | data: gbSig 30 | }); 31 | let vData = con.methods.guestBook(accts[0]).encodeABI(); 32 | let visits = await web3.eth.call({ 33 | to: p2.address, 34 | data: vData 35 | }); 36 | console.log("VISITORS", visits); 37 | }) 38 | } else { 39 | console.log("Upgrading contract with new deployment address"); 40 | let txnData = con.methods.updateCode(d.address).encodeABI(); 41 | await web3.eth.sendTransaction({ 42 | from: accts[0], 43 | to: p.address, 44 | data: txnData, 45 | gas: 600000, 46 | gasPrice: 10000000000 47 | }); 48 | console.log("New address applied, verifying"); 49 | await new Promise((done)=>{ 50 | setTimeout(async ()=>{ 51 | //wait for txn confirmation so that new address is actually established 52 | /* 53 | let gbSig = con.methods.visit().encodeABI(); 54 | await web3.eth.sendTransaction({ 55 | from: accts[1], 56 | to: p.address, 57 | data: gbSig 58 | }); 59 | */ 60 | let vData = con.methods.guestBook(accts[0]).encodeABI(); 61 | let visits = await web3.eth.call({ 62 | to: p.address, 63 | data: vData 64 | }); 65 | console.log("Old visits log entry", visits); 66 | 67 | let cAddrTxn = con.methods.codeAddress().encodeABI(); 68 | let cAddr = await web3.eth.call({ 69 | to: p.address, 70 | data: cAddrTxn 71 | }); 72 | console.log("CAddr", cAddr); 73 | done(); 74 | }, 2000); 75 | }) 76 | } 77 | }) 78 | 79 | }; 80 | -------------------------------------------------------------------------------- /templates/solidity/LibraryLock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.1; 2 | 3 | import './LibraryLockDataLayout.sol'; 4 | 5 | contract LibraryLock is LibraryLockDataLayout { 6 | // Ensures no one can manipulate the Logic Contract once it is deployed. 7 | // PARITY WALLET HACK PREVENTION 8 | 9 | modifier delegatedOnly() { 10 | require(initialized == true, "The library is locked. No direct 'call' is allowed"); 11 | _; 12 | } 13 | function initialize() internal { 14 | initialized = true; 15 | } 16 | } -------------------------------------------------------------------------------- /templates/solidity/LibraryLockDataLayout.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.1; 2 | 3 | contract LibraryLockDataLayout { 4 | bool public initialized = false; 5 | } -------------------------------------------------------------------------------- /templates/solidity/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.25 <0.6.0; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | modifier restricted() { 8 | if (msg.sender == owner) _; 9 | } 10 | 11 | constructor() public { 12 | owner = msg.sender; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /templates/solidity/Owned.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.1; 2 | 3 | contract Owned { 4 | 5 | address owner; 6 | 7 | function setOwner(address _owner) internal { 8 | owner = _owner; 9 | } 10 | modifier onlyOwner() { 11 | require(msg.sender == owner, "Only owner is allowed to perform this action"); 12 | _; 13 | } 14 | } -------------------------------------------------------------------------------- /templates/solidity/Proxiable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.1; 2 | 3 | contract Proxiable { 4 | // Code position in storage is keccak256("PROXIABLE") = "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7" 5 | 6 | function updateCodeAddress(address newAddress) internal { 7 | require( 8 | bytes32(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7) == Proxiable(newAddress).proxiableUUID(), 9 | "Not compatible" 10 | ); 11 | assembly { // solium-disable-line 12 | sstore(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7, newAddress) 13 | } 14 | } 15 | function proxiableUUID() public pure returns (bytes32) { 16 | return 0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7; 17 | } 18 | } -------------------------------------------------------------------------------- /templates/solidity/Proxy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.1; 2 | 3 | contract Proxy { 4 | // Code position in storage is keccak256("PROXIABLE") = "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7" 5 | constructor(bytes memory constructData, address contractLogic) public { 6 | // save the code address 7 | assembly { // solium-disable-line 8 | sstore(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7, contractLogic) 9 | } 10 | (bool success, bytes memory _) = contractLogic.delegatecall(constructData); // solium-disable-line 11 | require(success); 12 | } 13 | 14 | function() external payable { 15 | assembly { // solium-disable-line 16 | let contractLogic := sload(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7) 17 | calldatacopy(0x0, 0x0, calldatasize) 18 | let success := delegatecall(sub(gas, 10000), contractLogic, 0x0, calldatasize, 0, 0) 19 | let retSz := returndatasize 20 | returndatacopy(0, 0, retSz) 21 | switch success 22 | case 0 { 23 | revert(0, retSz) 24 | } 25 | default { 26 | return(0, retSz) 27 | } 28 | } 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /templates/solidity/Sample.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.1; 2 | 3 | import './Proxiable.sol'; 4 | import './Owned.sol'; 5 | import './SampleDataLayout.sol'; 6 | import './LibraryLock.sol'; 7 | 8 | contract Sample is SampleDataLayout, Owned, Proxiable, LibraryLock { 9 | function postConstructor(uint256 max) public { 10 | require(!initialized); 11 | maxGuests = max; 12 | initialize(); 13 | setOwner(msg.sender); 14 | address myAddr = codeAddress(); 15 | emit NewContractAddress(myAddr); 16 | } 17 | 18 | /** 19 | * This function should normally be guarded by onlyOwner. The modifier was 20 | * removed for the purposes of the demo. 21 | * 22 | * Because the proxy-test ganache instance running a forked chain uses 23 | * different accounts than those used to deploy the contract on the 24 | * original chain, it does not have access to the owner account. 25 | * 26 | * It is important for the proxy-test feature to run the upgrade pattern 27 | * because it needs to verify that an upgrade deployed live will work 28 | * properly. 29 | */ 30 | function updateCode(address newAddress) public delegatedOnly { 31 | updateCodeAddress(newAddress); 32 | emit NewContractAddress(newAddress) 33 | } 34 | 35 | function visit() public delegatedOnly { 36 | require(!guestBook[msg.sender], "Guest exists"); 37 | guestBook[msg.sender] = true; 38 | } 39 | 40 | function increment() public delegatedOnly { 41 | count += 1; 42 | } 43 | 44 | function codeAddress() public delegatedOnly returns (address cAddr) { 45 | assembly { 46 | cAddr := sload(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7) 47 | 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /templates/solidity/SampleDataLayout.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.1; 2 | 3 | import './LibraryLockDataLayout.sol'; 4 | 5 | contract SampleDataLayout is LibraryLockDataLayout { 6 | event NewContractAddress(address newAddress); 7 | 8 | uint public count; 9 | uint public maxGuests; 10 | mapping(address=>bool) public guestBook; 11 | } -------------------------------------------------------------------------------- /templates/test/sample.js: -------------------------------------------------------------------------------- 1 | const Proxy = artifacts.require("Proxy"); 2 | const Sample = artifacts.require("Sample") 3 | const BN = web3.utils.BN; 4 | 5 | contract("Sample", accounts => { 6 | // it("should put 10000 Sample in the first account", () => 7 | // Sample.deployed() 8 | // .then(instance => instance.getBalance.call(accounts[0])) 9 | // .then(balance => { 10 | // assert.equal( 11 | // balance.valueOf(), 12 | // 10000, 13 | // "10000 wasn't in the first account" 14 | // ); 15 | // })); 16 | // 17 | // it("should not call visit", () => { 18 | // let meta; 19 | // let metaCoinBalance; 20 | // let metaCoinEthBalance; 21 | // 22 | // return Sample.deployed() 23 | // .then(instance => { 24 | // meta = instance; 25 | // return meta.visit.call(); 26 | // }); 27 | // }); 28 | // 29 | // it("should have one visitor", async () => { 30 | // 31 | // const accounts = await web3.eth.getAccounts(); 32 | // const account = accounts[0]; 33 | // 34 | // const sampleDeployment = await Sample.deployed(); 35 | // const proxyDeployment = await Proxy.deployed(); 36 | // 37 | // const sampleContractInstance = new web3.eth.Contract( 38 | // sampleDeployment.abi, 39 | // sampleDeployment.address, 40 | // { address: sampleDeployment.address } 41 | // ); 42 | // 43 | // const vData = sampleContractInstance.methods.guestBook(account).encodeABI(); 44 | // 45 | // const visits = await web3.eth.call({ 46 | // to: proxyDeployment.address, 47 | // data: vData 48 | // }); 49 | // 50 | // console.log({visits: visits}); 51 | // 52 | // }); 53 | // 54 | // it("should prevent guest signing twice", async () => { 55 | // 56 | // const accounts = await web3.eth.getAccounts(); 57 | // const account = accounts[0]; 58 | // 59 | // const sampleDeployment = await Sample.deployed(); 60 | // const proxyDeployment = await Proxy.deployed(); 61 | // 62 | // const sampleContractInstance = new web3.eth.Contract( 63 | // sampleDeployment.abi, 64 | // sampleDeployment.address, 65 | // { address: sampleDeployment.address } 66 | // ); 67 | // 68 | // const vData = sampleContractInstance.methods.guestBook(account).encodeABI(); 69 | // 70 | // const visits = await web3.eth.call({ 71 | // to: proxyDeployment.address, 72 | // data: vData 73 | // }); 74 | // 75 | // const visits2 = await web3.eth.call({ 76 | // to: proxyDeployment.address, 77 | // data: vData 78 | // }); 79 | // 80 | // console.log({visits: visits}); 81 | // 82 | // }); 83 | 84 | it("should be 5 max guests", async () => { 85 | 86 | const sampleDeployment = await Sample.deployed(); 87 | const proxyDeployment = await Proxy.deployed(); 88 | 89 | const sampleContractInstance = new web3.eth.Contract( 90 | sampleDeployment.abi, 91 | proxyDeployment.address, 92 | { address: proxyDeployment.address } 93 | ); 94 | 95 | const maxGuests = new BN(await sampleContractInstance.methods.maxGuests().call()).toNumber(); 96 | 97 | assert.equal(maxGuests, 5); 98 | }); 99 | 100 | 101 | it("should increment counter by 1", async () => { 102 | 103 | const accounts = await web3.eth.getAccounts(); 104 | const account = accounts[0]; 105 | 106 | const sampleDeployment = await Sample.deployed(); 107 | const proxyDeployment = await Proxy.deployed(); 108 | 109 | const sampleContractInstance = new web3.eth.Contract( 110 | sampleDeployment.abi, 111 | proxyDeployment.address, 112 | { address: proxyDeployment.address } 113 | ); 114 | 115 | const before = new BN(await sampleContractInstance.methods.count().call()).toNumber(); 116 | 117 | await sampleContractInstance.methods.increment().send({ 118 | from: account, 119 | gas: 600000, 120 | gasPrice: 100000000 121 | }); 122 | 123 | const after = new BN(await sampleContractInstance.methods.count().call()).toNumber(); 124 | 125 | assert.equal(before, after - 1); 126 | }); 127 | 128 | }); 129 | -------------------------------------------------------------------------------- /test-proxy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Main "truffle run test-proxy" entry point. 3 | */ 4 | 5 | module.exports = (config) => { 6 | const ganache = require('ganache-core'); 7 | const Web3 = require('web3'); 8 | const test = require('truffle-core/lib/commands/test'); 9 | const dir = require("node-dir"); 10 | 11 | const networkName = config['network'] || Object.keys(config['networks'])[0]; 12 | const network = config['networks'][networkName]; 13 | 14 | const localHost = '127.0.0.1'; 15 | const localPort = 7545; 16 | const protocol = 'http://'; 17 | const url = protocol + network.host + ':' + network.port; 18 | 19 | const web3 = new Web3(Web3.providers.HttpProvider(url)); 20 | 21 | web3.eth.getBlockNumber().then(blockNumber => { 22 | web3.eth.getAccounts().then(accounts => { 23 | const options = { 24 | port: localPort, 25 | network_id: network.network_id, 26 | fork: url + '@' + blockNumber 27 | }; 28 | 29 | const server = ganache.server(options); 30 | server.listen(localPort, (err, result) => { 31 | const testOptions = { 32 | _: [], 33 | network: networkName, 34 | networks: { 35 | [networkName]: { 36 | host: localHost, 37 | port: localPort, 38 | network_id: network.network_id, 39 | } 40 | } 41 | }; 42 | 43 | test.run(testOptions, err => { 44 | console.log(err); 45 | server.close(() => {}); 46 | }); 47 | }); 48 | }); 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /truffle-plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "commands": { 3 | "create-proxy": "create-proxy.js", 4 | "summarize-proxy": "summarize-proxy.js", 5 | "test-proxy": "test-proxy.js", 6 | "migrate-proxy": "migrate-proxy.js" 7 | } 8 | } 9 | --------------------------------------------------------------------------------