├── .editorconfig ├── .gitattributes ├── .gitignore ├── README.md ├── contracts ├── Impl.sol └── proxy │ ├── CloneFactory.sol │ ├── MinimalProxyFactory.sol │ ├── ProxyWithImmutable.sol │ ├── ProxyWithMemory.sol │ └── ProxyWithStorage.sol ├── package.json ├── scripts └── test.sh ├── test └── Proxy.test.js └── truffle-config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = space 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [*.js] 11 | indent_size = 2 12 | 13 | [*.sol] 14 | indent_size = 4 15 | 16 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | *.or-any-other-extension-you-like linguist-language=Solidity 3 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | artifacts 4 | build 5 | coverage.json 6 | package-lock.json 7 | **/.*.swp 8 | allFiredEvents 9 | crytic-export 10 | .coverage_artifacts 11 | .DS_Store 12 | 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Solidity Techniques 2 | 3 | ### Reducing deploy size 4 | 5 | 1. proxy 6 | 2. factory pattern 7 | 3. minimal proxy 8 | 4. proxy with immutables 9 | 5. proxy with storage slot allocation 10 | 6. proxy with creation code [link](https://solidity.readthedocs.io/en/v0.6.8/units-and-global-variables.html#meta-type) 11 | 7. [EXPERIMENTAL] proxy with memory 12 | 8. [HIGHLY EXPERIMENTAL] minimal proxy with memory 13 | 14 | ### Require statements 15 | 16 | 1. using require as control 17 | 2. require with error message 18 | 3. how error message works 19 | 20 | ### Function types 21 | 22 | 23 | -------------------------------------------------------------------------------- /contracts/Impl.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.6.8; 3 | 4 | contract Impl { 5 | address public _temp; 6 | uint256 public _stored; 7 | 8 | function hello() external pure returns(string memory message) { 9 | message = "Hello World"; 10 | } 11 | 12 | function store(uint256 value) external returns(bool) { 13 | _stored = value; 14 | return true; 15 | } 16 | 17 | function temp() external view returns(address) { 18 | return _temp; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /contracts/proxy/CloneFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.6.8; 3 | // code is copy and pasted from https://github.com/optionality/clone-factory/blob/master/contracts/CloneFactory.sol 4 | /* 5 | The MIT License (MIT) 6 | Copyright (c) 2018 Murray Software, LLC. 7 | Permission is hereby granted, free of charge, to any person obtaining 8 | a copy of this software and associated documentation files (the 9 | "Software"), to deal in the Software without restriction, including 10 | without limitation the rights to use, copy, modify, merge, publish, 11 | distribute, sublicense, and/or sell copies of the Software, and to 12 | permit persons to whom the Software is furnished to do so, subject to 13 | the following conditions: 14 | The above copyright notice and this permission notice shall be included 15 | in all copies or substantial portions of the Software. 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | */ 24 | //solhint-disable max-line-length 25 | //solhint-disable no-inline-assembly 26 | 27 | contract CloneFactory { 28 | 29 | function createClone(address target) internal returns (address result) { 30 | bytes20 targetBytes = bytes20(target); 31 | assembly { 32 | let clone := mload(0x40) 33 | mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000) 34 | mstore(add(clone, 0x14), targetBytes) 35 | mstore(add(clone, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000) 36 | result := create(0, clone, 0x37) 37 | } 38 | } 39 | 40 | function isClone(address target, address query) internal view returns (bool result) { 41 | bytes20 targetBytes = bytes20(target); 42 | assembly { 43 | let clone := mload(0x40) 44 | mstore(clone, 0x363d3d373d3d3d363d7300000000000000000000000000000000000000000000) 45 | mstore(add(clone, 0xa), targetBytes) 46 | mstore(add(clone, 0x1e), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000) 47 | 48 | let other := add(clone, 0x40) 49 | extcodecopy(query, other, 0, 0x2d) 50 | result := and( 51 | eq(mload(clone), mload(other)), 52 | eq(mload(add(clone, 0xd)), mload(add(other, 0xd))) 53 | ) 54 | } 55 | } 56 | } 57 | 58 | -------------------------------------------------------------------------------- /contracts/proxy/MinimalProxyFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.6.8; 3 | 4 | 5 | import "./CloneFactory.sol"; 6 | 7 | contract MinimalProxyFactory is CloneFactory { 8 | event Deploy(address clone); 9 | function deploy(address target) external returns(address clone) { 10 | clone = createClone(target); 11 | emit Deploy(clone); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /contracts/proxy/ProxyWithImmutable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.6.8; 3 | 4 | contract ProxyWithImmutable { 5 | address immutable public _impl; 6 | 7 | constructor(address impl) public { 8 | _impl = impl; 9 | } 10 | 11 | fallback() external payable { 12 | address impl = _impl; 13 | assembly { 14 | let _target := impl 15 | calldatacopy(0x0, 0x0, calldatasize()) 16 | let result := delegatecall(gas(), _target, 0x0, calldatasize(), 0x0, 0) 17 | returndatacopy(0x0, 0x0, returndatasize()) 18 | switch result case 0 {revert(0, 0)} default {return (0, returndatasize())} 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /contracts/proxy/ProxyWithMemory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.6.8; 3 | 4 | contract ProxyWithMemory { 5 | address immutable internal _impl; 6 | uint256 immutable internal _storage; 7 | constructor(address impl, uint256 value) public { 8 | _impl = impl; 9 | _storage = value; 10 | } 11 | 12 | fallback() external payable { 13 | address target = _impl; 14 | uint256 stored = _storage; 15 | assembly { 16 | mstore(0x40, stored) 17 | let _target := target 18 | calldatacopy(0x0, 0x0, calldatasize()) 19 | let result := delegatecall(gas(), _target, 0x0, calldatasize(), 0x0, 0) 20 | returndatacopy(0x0, 0x0, returndatasize()) 21 | switch result case 0 {revert(0, 0)} default {return (0, returndatasize())} 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /contracts/proxy/ProxyWithStorage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.6.8; 3 | 4 | contract ProxyWithStorage { 5 | address public _impl; 6 | 7 | constructor(address impl) public { 8 | _impl = impl; 9 | } 10 | 11 | fallback() external payable { 12 | assembly { 13 | let _target := sload(0) 14 | calldatacopy(0x0, 0x0, calldatasize()) 15 | let result := delegatecall(gas(), _target, 0x0, calldatasize(), 0x0, 0) 16 | returndatacopy(0x0, 0x0, returndatasize()) 17 | switch result case 0 {revert(0, 0)} default {return (0, returndatasize())} 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "solidity-techniques", 3 | "version": "1.0.0", 4 | "description": "solidity techniques explained", 5 | "main": "index.js", 6 | "scripts": { 7 | "compile": "npx truffle compile", 8 | "test": "./scripts/test.sh" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/leekt216/solidity-techniques.git" 13 | }, 14 | "keywords": [ 15 | "solidity" 16 | ], 17 | "author": "leekt216@gmail.com", 18 | "license": "GPL-3.0", 19 | "bugs": { 20 | "url": "https://github.com/leekt216/solidity-techniques/issues" 21 | }, 22 | "homepage": "https://github.com/leekt216/solidity-techniques#readme", 23 | "dependencies": { 24 | "chai": "^4.2.0", 25 | "ganache-cli": "^6.9.1", 26 | "solidity-coverage": "^0.7.5", 27 | "truffle": "^5.1.27", 28 | "truffle-hdwallet-provider": "^1.0.17", 29 | "truffle-hdwallet-provider-klaytn": "^1.0.13-a" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Exit script as soon as a command fails. 4 | set -o errexit 5 | 6 | # Executes cleanup function at script exit. 7 | trap cleanup EXIT 8 | 9 | cleanup() { 10 | # Kill the ganache instance that we started (if we started one and if it's still running). 11 | if [ -n "$ganache_pid" ] && ps -p $ganache_pid > /dev/null; then 12 | kill -9 $ganache_pid 13 | fi 14 | } 15 | 16 | if [ "$SOLIDITY_COVERAGE" = true ]; then 17 | ganache_port=8555 18 | else 19 | ganache_port=8545 20 | fi 21 | 22 | ganache_running() { 23 | netcat -z localhost "$ganache_port" 24 | } 25 | 26 | start_ganache() { 27 | local accounts=( 28 | # 10 accounts with balance 1M ether, needed for high-value tests. 29 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501200,1000000000000000000000000" 30 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501201,1000000000000000000000000" 31 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501202,1000000000000000000000000" 32 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501203,1000000000000000000000000" 33 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501204,1000000000000000000000000" 34 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501205,1000000000000000000000000" 35 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501206,1000000000000000000000000" 36 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501207,1000000000000000000000000" 37 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501208,1000000000000000000000000" 38 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501209,1000000000000000000000000" 39 | # 3 accounts to be used for GSN matters. 40 | --account="0x956b91cb2344d7863ea89e6945b753ca32f6d74bb97a59e59e04903ded14ad00,1000000000000000000000000" 41 | --account="0x956b91cb2344d7863ea89e6945b753ca32f6d74bb97a59e59e04903ded14ad01,1000000000000000000000000" 42 | --account="0x956b91cb2344d7863ea89e6945b753ca32f6d74bb97a59e59e04903ded14ad02,1000000000000000000000000" 43 | ) 44 | 45 | npx ganache-cli --gasLimit 0xfffffffffff --port "$ganache_port" "${accounts[@]}" > /dev/null & 46 | 47 | ganache_pid=$! 48 | 49 | echo "Waiting for ganache to launch on port "$ganache_port"..." 50 | 51 | while ! ganache_running; do 52 | sleep 0.1 # wait for 1/10 of the second before check again 53 | done 54 | 55 | echo "Ganache launched!" 56 | } 57 | 58 | if ganache_running; then 59 | echo "Using existing ganache instance" 60 | else 61 | echo "Starting our own ganache instance" 62 | start_ganache 63 | fi 64 | 65 | npx truffle version 66 | 67 | npx truffle test "$@" 68 | 69 | -------------------------------------------------------------------------------- /test/Proxy.test.js: -------------------------------------------------------------------------------- 1 | const ProxyStorageFactory = artifacts.require('ProxyWithStorage'); 2 | const ProxyImmutableFactory = artifacts.require('ProxyWithImmutable'); 3 | const MinimalProxyFactory = artifacts.require('MinimalProxyFactory'); 4 | const Impl = artifacts.require('Impl'); 5 | 6 | const { expect } = require('chai'); 7 | 8 | function debug(message) { 9 | console.log("\t\t" + message); 10 | } 11 | 12 | contract('Proxy', function(accounts){ 13 | context('Deployment Gas', function(){ 14 | context('Non Proxy', function(){ 15 | beforeEach(async function(){ 16 | this.impl = await Impl.new(); 17 | this.deployment = (await web3.eth.getTransactionReceipt(this.impl.transactionHash)).gasUsed; 18 | }); 19 | it('deploy gas usage', async function(){ 20 | debug(this.deployment); 21 | }); 22 | it('function call gas usage', async function(){ 23 | const receipt = await this.impl.store(100); 24 | debug(receipt.receipt.gasUsed); 25 | }); 26 | }); 27 | 28 | context('storage', function(){ 29 | beforeEach(async function(){ 30 | this.impl = await Impl.new(); 31 | const temp = await ProxyStorageFactory.new(this.impl.address); 32 | this.deployment = (await web3.eth.getTransactionReceipt(temp.transactionHash)).gasUsed; 33 | this.proxy = await Impl.at(temp.address); 34 | }); 35 | it('deploy gas usage', async function(){ 36 | debug(this.deployment); 37 | }); 38 | 39 | it('should return Hello World through proxy', async function(){ 40 | const message = await this.proxy.hello(); 41 | expect(message).to.be.equal('Hello World'); 42 | }); 43 | it('function call gas usage', async function(){ 44 | const receipt = await this.proxy.store(100); 45 | debug(receipt.receipt.gasUsed); 46 | }); 47 | }); 48 | 49 | context('Immutable', function(){ 50 | beforeEach(async function(){ 51 | this.impl = await Impl.new(); 52 | const temp = await ProxyImmutableFactory.new(this.impl.address); 53 | this.deployment = (await web3.eth.getTransactionReceipt(temp.transactionHash)).gasUsed; 54 | this.proxy = await Impl.at(temp.address); 55 | }); 56 | 57 | it('deploy gas usage', async function(){ 58 | debug(this.deployment); 59 | }); 60 | 61 | it('should return Hello World through proxy', async function(){ 62 | const message = await this.proxy.hello(); 63 | expect(message).to.be.equal('Hello World'); 64 | }); 65 | 66 | it('function call gas usage', async function(){ 67 | const receipt = await this.proxy.store(100); 68 | debug(receipt.receipt.gasUsed); 69 | }); 70 | }); 71 | 72 | context('MinimalProxy', function(){ 73 | beforeEach(async function(){ 74 | this.impl = await Impl.new(); 75 | const factory = await MinimalProxyFactory.new(); 76 | const receipt = await factory.deploy(this.impl.address); 77 | const address = receipt.logs[0].args[0]; 78 | this.deployment = receipt.receipt.gasUsed; 79 | this.proxy = await Impl.at(address); 80 | }); 81 | 82 | it('deploy gas usage', async function(){ 83 | debug(this.deployment); 84 | }); 85 | 86 | it('should return Hello World through proxy', async function(){ 87 | const message = await this.proxy.hello(); 88 | expect(message).to.be.equal('Hello World'); 89 | }); 90 | 91 | it('function call gas usage', async function(){ 92 | const receipt = await this.proxy.store(100); 93 | debug(receipt.receipt.gasUsed); 94 | }); 95 | }); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | const HDWalletProvider = require('truffle-hdwallet-provider'); 2 | const KlaytnWalletProvider = require("truffle-hdwallet-provider-klaytn"); 3 | const mnemonic = 'hello hello'; 4 | const privateKey = ''; 5 | const infuraKey = ''; 6 | module.exports = { 7 | plugins: ['solidity-coverage'], 8 | networks: { 9 | mainnet: { 10 | provider: function() { 11 | return new HDWalletProvider(mnemonic, `https://mainnet.infura.io/v3/${infurakey}`); 12 | }, 13 | port: 8545, 14 | network_id: 1, 15 | }, 16 | ropsten: { 17 | provider: function() { 18 | return new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/${infuraKey}`); 19 | }, 20 | port: 8545, 21 | network_id: '3', 22 | skipDryRun: true, 23 | }, 24 | rinkeby: { 25 | provider: function() { 26 | return new HDWalletProvider(mnemonic, `https://rinkeby.infura.io/v3/${infuraKey}`); 27 | }, 28 | port: 8545, 29 | network_id: '4', 30 | skipDryRun: true, 31 | }, 32 | development: { 33 | host: 'localhost', 34 | port: 8545, 35 | network_id: '*', 36 | }, 37 | coverage: { 38 | host: 'localhost', 39 | network_id: '*', 40 | port: 8555, 41 | gas: 0xfffffffffff, 42 | gasPrice: 0x01, 43 | } 44 | }, 45 | compilers: { 46 | solc: { 47 | version: '0.6.8', 48 | settings: { 49 | optimizer: { 50 | enabled: true, 51 | runs: 200, 52 | }, 53 | evmVersion: '', //basically verstion defult, petersburg, istanbul use petersburg for klaytn 54 | }, 55 | }, 56 | }, 57 | }; 58 | --------------------------------------------------------------------------------