mirror of
https://github.com/Instadapp/infinite-proxy.git
synced 2024-07-29 21:47:49 +00:00
commit
fdefef58d6
3
.env.example
Normal file
3
.env.example
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
ETHERSCAN_API_KEY=ABC123ABC123ABC123ABC123ABC123ABC1
|
||||
ALCHEMY_API_KEY=<YOUR ALCHEMY KEY>
|
||||
PRIVATE_KEY=0xabc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1
|
||||
4
.eslintignore
Normal file
4
.eslintignore
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
node_modules
|
||||
artifacts
|
||||
cache
|
||||
coverage
|
||||
24
.eslintrc.js
Normal file
24
.eslintrc.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
module.exports = {
|
||||
env: {
|
||||
browser: false,
|
||||
es2021: true,
|
||||
mocha: true,
|
||||
node: true,
|
||||
},
|
||||
plugins: ["@typescript-eslint"],
|
||||
extends: [
|
||||
"standard",
|
||||
"plugin:prettier/recommended",
|
||||
"plugin:node/recommended",
|
||||
],
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
ecmaVersion: 12,
|
||||
},
|
||||
rules: {
|
||||
"node/no-unsupported-features/es-syntax": [
|
||||
"error",
|
||||
{ ignores: ["modules"] },
|
||||
],
|
||||
},
|
||||
};
|
||||
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
node_modules
|
||||
.env
|
||||
coverage
|
||||
coverage.json
|
||||
typechain
|
||||
|
||||
#Hardhat files
|
||||
cache
|
||||
artifacts
|
||||
3
.npmignore
Normal file
3
.npmignore
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
hardhat.config.ts
|
||||
scripts
|
||||
test
|
||||
5
.prettierignore
Normal file
5
.prettierignore
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
node_modules
|
||||
artifacts
|
||||
cache
|
||||
coverage*
|
||||
gasReporterOutput.json
|
||||
1
.prettierrc
Normal file
1
.prettierrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
{}
|
||||
7
.solhint.json
Normal file
7
.solhint.json
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"extends": "solhint:recommended",
|
||||
"rules": {
|
||||
"compiler-version": ["error", "^0.8.0"],
|
||||
"func-visibility": ["warn", { "ignoreConstructors": true }]
|
||||
}
|
||||
}
|
||||
1
.solhintignore
Normal file
1
.solhintignore
Normal file
|
|
@ -0,0 +1 @@
|
|||
node_modules
|
||||
21
README.md
21
README.md
|
|
@ -1,11 +1,24 @@
|
|||
# infinite-proxy (Infinite Extendable Proxy)
|
||||
|
||||
Upgradable proxy with infinite implementations enabled at once.
|
||||
Upgradeable proxy with infinite implementations enabled at once.
|
||||
|
||||
Read about general upgradable contacts with 1 implementation contract [here](https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable).
|
||||
Read about general upgradeable contacts with 1 implementation contract [here](https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable).
|
||||
|
||||
### Details
|
||||
## Introduction
|
||||
Proxies are a big part of ethereum development, they solve many problems faced by developers while writing smart contracts. Still there are some problems which aren't solved via normal proxies. Eg:- Ethereum code-limit issue, deployment of full contract again for updates in small part of the code, unability to easily disable/enable some functions.
|
||||
So whats the solution? Normal proxies have the implementation address stored (in the implementaion slot) to which they delegate call for every external call. What if there could be multiple implementations for a single proxy, the proxy able to decide which implementation to delegate call to for a particular external call. This solves the ethereum-code limit issue as the code can be separated out into different implementations, for smaller updates to the code only that particular implementation has to be redeployed, lastly functions can be easily disabled/enabled by removing/adding implementations.
|
||||
|
||||
## How it works?
|
||||
- Creates a mapping from bytes4 sig to implementation's address
|
||||
- Stores mapping from implementation's address to bytes4[] sigs. All the external functions we want to be callable from our contract.
|
||||
- Every call (other than addition & removal of implementation & sigs) goes through fallback.
|
||||
- In fallback it fetches the msg.sig, fetches the implementation from it and run the code logic on that.
|
||||
- In fallback it fetches the msg.sig, fetches the implementation from it and runs the code logic on that.
|
||||
|
||||
## How to use it?
|
||||
- Proxies and implementations are deployed separately, then multiple(infinite) implementations can be added to the proxy. One thing to make sure is the storage variables of all implementations should be at the same storage slot so storage don't get messed up (similar to what we do in upgradable proxy).
|
||||
- This repo contains an example of using infinite proxies. Checkout the example contracts. Different implementations have been separated out into different modules, but they use the same variables contract.
|
||||
- Deployment of infinite-proxies is also a little different from normal proxies, the repo also contains an example deployment script.
|
||||
|
||||
## Dummy Implementation
|
||||
One issue with infinite proxies is that etherscan isn't able to detect that the contract is a proxy and hence isn't able to link it to the implementations. This makes it not usable via etherscan, also users can't see the list of all the read/write functions they can call.
|
||||
An innovative solution for the problem is using a dummy implemenatation. A dummy implementation is nothing but a contract containing all external functions which are callable by the users. The dummy implementation doesn't contain any logics of the functions (analogous to contract interfaces). The dummy implementation's address is stored at the implementation slot which etherscan detects in order to link proxies to their implementations, hence all the external functions can then be seen/called directly via etherscan. Through dummy impelementations, etherscan gets the functions sigs, generates the calldata, but the actual call is delegated to the respective implementation.
|
||||
|
|
|
|||
12
contracts/example/common/variables.sol
Normal file
12
contracts/example/common/variables.sol
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
contract Constants {
|
||||
// token supported
|
||||
address public constant token = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; // USDC
|
||||
}
|
||||
|
||||
contract Variables is Constants {
|
||||
// userAddress => amount deposited
|
||||
mapping(address => uint256) internal _userBalance;
|
||||
}
|
||||
26
contracts/example/dummyImplementation.sol
Normal file
26
contracts/example/dummyImplementation.sol
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
contract UserModule {
|
||||
/**
|
||||
* @dev User function to supply.
|
||||
* @param amount_ amount to supply.
|
||||
*/
|
||||
function supply(uint256 amount_) external {}
|
||||
|
||||
/**
|
||||
* @dev User function to withdraw.
|
||||
* @param amount_ amount to withdraw.
|
||||
*/
|
||||
function withdraw(uint256 amount_) external {}
|
||||
}
|
||||
|
||||
contract ReadModule {
|
||||
/**
|
||||
* @dev Read function to get user's balance in the contract.
|
||||
* @param user_ address of user.
|
||||
*/
|
||||
function userBalance(address user_) public view returns (uint256) {}
|
||||
}
|
||||
|
||||
contract DummyImplementation is UserModule, ReadModule {}
|
||||
10
contracts/example/module1/events.sol
Normal file
10
contracts/example/module1/events.sol
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "../common/variables.sol";
|
||||
|
||||
contract Events is Variables {
|
||||
event supplyLog(uint256 amount_);
|
||||
|
||||
event withdrawLog(uint256 amount_);
|
||||
}
|
||||
32
contracts/example/module1/main.sol
Normal file
32
contracts/example/module1/main.sol
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "./events.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
|
||||
contract UserModule is Events {
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
/**
|
||||
* @dev User function to supply.
|
||||
* @param amount_ amount to supply.
|
||||
*/
|
||||
function supply(uint256 amount_) external {
|
||||
IERC20(token).safeTransferFrom(msg.sender, address(this), amount_);
|
||||
_userBalance[msg.sender] += amount_;
|
||||
|
||||
emit supplyLog(amount_);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev User function to withdraw.
|
||||
* @param amount_ amount to withdraw.
|
||||
*/
|
||||
function withdraw(uint256 amount_) external {
|
||||
_userBalance[msg.sender] -= amount_;
|
||||
IERC20(token).safeTransfer(msg.sender, amount_);
|
||||
|
||||
emit withdrawLog(amount_);
|
||||
}
|
||||
}
|
||||
14
contracts/example/module2/main.sol
Normal file
14
contracts/example/module2/main.sol
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "../common/variables.sol";
|
||||
|
||||
contract ReadModule is Variables {
|
||||
/**
|
||||
* @dev Read function to get user's balance in the contract.
|
||||
* @param user_ address of user.
|
||||
*/
|
||||
function userBalance(address user_) public view returns (uint256) {
|
||||
return _userBalance[user_];
|
||||
}
|
||||
}
|
||||
10
contracts/example/proxy.sol
Normal file
10
contracts/example/proxy.sol
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "../infiniteProxy/proxy.sol";
|
||||
|
||||
contract Example is Proxy {
|
||||
constructor(address admin_, address dummyImplementation_)
|
||||
Proxy(admin_, dummyImplementation_)
|
||||
{}
|
||||
}
|
||||
24
contracts/infiniteProxy/IProxy.sol
Normal file
24
contracts/infiniteProxy/IProxy.sol
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
interface IProxy {
|
||||
function setAdmin(address newAdmin_) external;
|
||||
|
||||
function setDummyImplementation(address newDummyImplementation_) external;
|
||||
|
||||
function addImplementation(address implementation_, bytes4[] calldata sigs_)
|
||||
external;
|
||||
|
||||
function removeImplementation(address implementation_) external;
|
||||
|
||||
function getAdmin() external view returns (address);
|
||||
|
||||
function getDummyImplementation() external view returns (address);
|
||||
|
||||
function getImplementationSigs(address impl_)
|
||||
external
|
||||
view
|
||||
returns (bytes4[] memory);
|
||||
|
||||
function getSigsImplementation(bytes4 sig_) external view returns (address);
|
||||
}
|
||||
|
|
@ -32,6 +32,9 @@ contract Internals is Events {
|
|||
bytes32 internal constant _DUMMY_IMPLEMENTATION_SLOT =
|
||||
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
|
||||
|
||||
/**
|
||||
* @dev Returns the storage slot which stores the sigs array set for the implementation.
|
||||
*/
|
||||
function _getSlotImplSigsSlot(address implementation_)
|
||||
internal
|
||||
pure
|
||||
|
|
@ -43,6 +46,9 @@ contract Internals is Events {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns the storage slot which stores the implementation address for the function sig.
|
||||
*/
|
||||
function _getSlotSigsImplSlot(bytes4 sig_) internal pure returns (bytes32) {
|
||||
return keccak256(abi.encode("eip1967.proxy.implementation", sig_));
|
||||
}
|
||||
|
|
@ -97,7 +103,7 @@ contract Internals is Events {
|
|||
}
|
||||
|
||||
/**
|
||||
* @dev removes implementation and the mappings corresponding to it.
|
||||
* @dev Removes implementation and the mappings corresponding to it.
|
||||
*/
|
||||
function _removeImplementationSigs(address implementation_) internal {
|
||||
bytes32 slot_ = _getSlotImplSigsSlot(implementation_);
|
||||
|
|
@ -111,6 +117,9 @@ contract Internals is Events {
|
|||
emit removeImplementationLog(implementation_);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns bytes4[] sigs from implementation address. If implemenatation is not registered then returns empty array.
|
||||
*/
|
||||
function _getImplementationSigs(address implementation_)
|
||||
internal
|
||||
view
|
||||
|
|
@ -120,6 +129,9 @@ contract Internals is Events {
|
|||
return getSigsSlot(slot_).value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns implementation address from bytes4 sig. If sig is not registered then returns address(0).
|
||||
*/
|
||||
function _getSigImplementation(bytes4 sig_)
|
||||
internal
|
||||
view
|
||||
|
|
@ -223,20 +235,23 @@ contract Internals is Events {
|
|||
}
|
||||
|
||||
contract AdminStuff is Internals {
|
||||
/**
|
||||
* @dev Only admin gaurd.
|
||||
*/
|
||||
modifier onlyAdmin() {
|
||||
require(msg.sender == _getAdmin(), "not-the-admin");
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev sets new admin.
|
||||
* @dev Sets new admin.
|
||||
*/
|
||||
function setAdmin(address newAdmin_) external onlyAdmin {
|
||||
_setAdmin(newAdmin_);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev sets new dummy-implementation.
|
||||
* @dev Sets new dummy-implementation.
|
||||
*/
|
||||
function setDummyImplementation(address newDummyImplementation_)
|
||||
external
|
||||
|
|
@ -246,7 +261,7 @@ contract AdminStuff is Internals {
|
|||
}
|
||||
|
||||
/**
|
||||
* @dev adds new implementation address.
|
||||
* @dev Adds new implementation address.
|
||||
*/
|
||||
function addImplementation(address implementation_, bytes4[] calldata sigs_)
|
||||
external
|
||||
|
|
@ -256,7 +271,7 @@ contract AdminStuff is Internals {
|
|||
}
|
||||
|
||||
/**
|
||||
* @dev removes an existing implementation address.
|
||||
* @dev Removes an existing implementation address.
|
||||
*/
|
||||
function removeImplementation(address implementation_) external onlyAdmin {
|
||||
_removeImplementationSigs(implementation_);
|
||||
|
|
@ -274,21 +289,21 @@ abstract contract Proxy is AdminStuff {
|
|||
{}
|
||||
|
||||
/**
|
||||
* @dev returns admin's address.
|
||||
* @dev Returns admin's address.
|
||||
*/
|
||||
function getAdmin() external view returns (address) {
|
||||
return _getAdmin();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev returns dummy-implementations's address.
|
||||
* @dev Returns dummy-implementations's address.
|
||||
*/
|
||||
function getDummyImplementation() external view returns (address) {
|
||||
return _getDummyImplementation();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev returns bytes4[] sigs from implementation address If not registered then returns empty array.
|
||||
* @dev Returns bytes4[] sigs from implementation address If not registered then returns empty array.
|
||||
*/
|
||||
function getImplementationSigs(address impl_)
|
||||
external
|
||||
|
|
@ -299,7 +314,7 @@ abstract contract Proxy is AdminStuff {
|
|||
}
|
||||
|
||||
/**
|
||||
* @dev returns implementation address from bytes4 sig. If sig is not registered then returns address(0).
|
||||
* @dev Returns implementation address from bytes4 sig. If sig is not registered then returns address(0).
|
||||
*/
|
||||
function getSigsImplementation(bytes4 sig_)
|
||||
external
|
||||
90
hardhat.config.ts
Normal file
90
hardhat.config.ts
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
import * as dotenv from "dotenv";
|
||||
|
||||
import { HardhatUserConfig, task } from "hardhat/config";
|
||||
import "@nomiclabs/hardhat-etherscan";
|
||||
import "@nomiclabs/hardhat-waffle";
|
||||
import "@typechain/hardhat";
|
||||
import "hardhat-gas-reporter";
|
||||
import "solidity-coverage";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
// This is a sample Hardhat task. To learn how to create your own go to
|
||||
// https://hardhat.org/guides/create-task.html
|
||||
task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
|
||||
const accounts = await hre.ethers.getSigners();
|
||||
|
||||
for (const account of accounts) {
|
||||
console.log(account.address);
|
||||
}
|
||||
});
|
||||
|
||||
const alchemyApiKey = process.env.ALCHEMY_API_KEY;
|
||||
if (!alchemyApiKey) {
|
||||
throw new Error("Please set your ALCHEMY_ETH_API_KEY in a .env file");
|
||||
}
|
||||
|
||||
// You need to export an object to set up your config
|
||||
// Go to https://hardhat.org/config/ to learn more
|
||||
|
||||
const config: HardhatUserConfig = {
|
||||
solidity: {
|
||||
compilers: [
|
||||
{
|
||||
version: "0.8.6",
|
||||
settings: {
|
||||
optimizer: {
|
||||
enabled: true,
|
||||
runs: 200,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
version: "0.7.6",
|
||||
settings: {
|
||||
optimizer: {
|
||||
enabled: true,
|
||||
runs: 200,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
version: "0.5.6",
|
||||
settings: {
|
||||
optimizer: {
|
||||
enabled: true,
|
||||
runs: 200,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
networks: {
|
||||
hardhat: {
|
||||
forking: {
|
||||
url: `https://eth-mainnet.alchemyapi.io/v2/${alchemyApiKey}`,
|
||||
// blockNumber: 14501064
|
||||
},
|
||||
chainId: 1,
|
||||
gasPrice: 151101000000,
|
||||
},
|
||||
mainnet: {
|
||||
url: `https://eth-mainnet.alchemyapi.io/v2/${alchemyApiKey}`,
|
||||
chainId: 1,
|
||||
gasPrice: 52101000000,
|
||||
accounts: [`0x${process.env.PRIVATE_KEY}`]
|
||||
},
|
||||
},
|
||||
gasReporter: {
|
||||
enabled: process.env.REPORT_GAS !== undefined,
|
||||
currency: "USD",
|
||||
},
|
||||
etherscan: {
|
||||
apiKey: process.env.ETHERSCAN_API_KEY,
|
||||
},
|
||||
mocha: {
|
||||
timeout: 10000 * 10000,
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
41007
package-lock.json
generated
Normal file
41007
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
54
package.json
Normal file
54
package.json
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
"name": "infinite-proxy",
|
||||
"version": "1.0.0",
|
||||
"description": "Upgradable proxy with infinite implementations enabled at once.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/Instadapp/infinite-proxy.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/Instadapp/infinite-proxy/issues"
|
||||
},
|
||||
"homepage": "https://github.com/Instadapp/infinite-proxy#readme",
|
||||
"devDependencies": {
|
||||
"@nomiclabs/hardhat-ethers": "^2.0.5",
|
||||
"@nomiclabs/hardhat-etherscan": "^3.0.3",
|
||||
"@nomiclabs/hardhat-waffle": "^2.0.3",
|
||||
"@typechain/ethers-v5": "^7.2.0",
|
||||
"@typechain/hardhat": "^2.3.1",
|
||||
"@types/chai": "^4.3.1",
|
||||
"@types/mocha": "^9.1.0",
|
||||
"@types/node": "^12.20.48",
|
||||
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
||||
"@typescript-eslint/parser": "^4.33.0",
|
||||
"chai": "^4.3.6",
|
||||
"dotenv": "^10.0.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-config-standard": "^16.0.3",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-prettier": "^3.4.1",
|
||||
"eslint-plugin-promise": "^5.2.0",
|
||||
"ethereum-waffle": "^3.4.4",
|
||||
"ethers": "^5.6.4",
|
||||
"hardhat": "^2.9.3",
|
||||
"hardhat-gas-reporter": "^1.0.8",
|
||||
"prettier": "^2.6.2",
|
||||
"prettier-plugin-solidity": "^1.0.0-beta.13",
|
||||
"solhint": "^3.3.7",
|
||||
"solidity-coverage": "^0.7.20",
|
||||
"ts-node": "^10.7.0",
|
||||
"typechain": "^5.2.0",
|
||||
"typescript": "^4.6.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@openzeppelin/contracts": "^4.5.0"
|
||||
}
|
||||
}
|
||||
129
scripts/deploy.ts
Normal file
129
scripts/deploy.ts
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
// We require the Hardhat Runtime Environment explicitly here. This is optional
|
||||
// but useful for running the script in a standalone fashion through `node <script>`.
|
||||
//
|
||||
// When running the script with `npx hardhat run <script>` you'll find the Hardhat
|
||||
// Runtime Environment's members available in the global scope.
|
||||
import { ethers } from "hardhat";
|
||||
import { Contract } from "ethers";
|
||||
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
|
||||
const hre = require("hardhat");
|
||||
|
||||
const UserModuleSigs = [
|
||||
"supply(uint256)",
|
||||
"withdraw(uint256)"
|
||||
].map((a) => ethers.utils.id(a).slice(0, 10));
|
||||
|
||||
const ReadModuleSigs = [
|
||||
"userBalance(address)"
|
||||
].map((a) => ethers.utils.id(a).slice(0, 10));
|
||||
|
||||
async function main() {
|
||||
// Hardhat always runs the compile task when running scripts with its command
|
||||
// line interface.
|
||||
//
|
||||
// If this script is run directly using `node` you may want to call compile
|
||||
// manually to make sure everything is compiled
|
||||
// await hre.run('compile');
|
||||
|
||||
// We get the contract to deploy
|
||||
let proxy: Contract,
|
||||
userModule: Contract,
|
||||
readModule: Contract,
|
||||
dummyImplementation: Contract,
|
||||
signer: SignerWithAddress
|
||||
|
||||
[signer] = await ethers.getSigners();
|
||||
const proxyAdmin = signer.address
|
||||
|
||||
const UserModule = await ethers.getContractFactory(
|
||||
"contracts/example/module1/main.sol:UserModule"
|
||||
);
|
||||
userModule = await UserModule.deploy();
|
||||
await userModule.deployed();
|
||||
console.log("User module deployed to: ", userModule.address);
|
||||
|
||||
const ReadModule = await ethers.getContractFactory(
|
||||
"contracts/example/module2/main.sol:ReadModule"
|
||||
);
|
||||
readModule = await ReadModule.deploy();
|
||||
await readModule.deployed();
|
||||
console.log("Read module deployed to: ", readModule.address);
|
||||
|
||||
const DummyImplementation = await ethers.getContractFactory(
|
||||
"contracts/example/dummyImplementation.sol:DummyImplementation"
|
||||
);
|
||||
dummyImplementation = await DummyImplementation.deploy();
|
||||
await dummyImplementation.deployed();
|
||||
console.log(
|
||||
"Dummy Implementation deployed to: ",
|
||||
dummyImplementation.address
|
||||
);
|
||||
|
||||
const Proxy = await ethers.getContractFactory(
|
||||
"contracts/example/proxy.sol:Example"
|
||||
);
|
||||
proxy = await Proxy.deploy(proxyAdmin, dummyImplementation.address);
|
||||
await proxy.deployed();
|
||||
console.log("Proxy deployed to: ", proxy.address);
|
||||
|
||||
let tx = await proxy.addImplementation(userModule.address, UserModuleSigs);
|
||||
await tx.wait();
|
||||
console.log("User Module implementation enabled!");
|
||||
|
||||
tx = await proxy.addImplementation(readModule.address, ReadModuleSigs);
|
||||
await tx.wait();
|
||||
console.log("Read Module implementation enabled!");
|
||||
|
||||
try {
|
||||
await hre.run("verify:verify", {
|
||||
address: userModule.address,
|
||||
constructorArguments: [],
|
||||
contract: "contracts/example/module1/main.sol:UserModule",
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Failed to verify User Module");
|
||||
console.log(error);
|
||||
console.log();
|
||||
}
|
||||
try {
|
||||
await hre.run("verify:verify", {
|
||||
address: readModule.address,
|
||||
constructorArguments: [],
|
||||
contract: "contracts/example/module2/main.sol:ReadModule",
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Failed to verify Read Module");
|
||||
console.log(error);
|
||||
console.log();
|
||||
}
|
||||
try {
|
||||
await hre.run("verify:verify", {
|
||||
address: dummyImplementation.address,
|
||||
constructorArguments: [],
|
||||
contract:
|
||||
"contracts/example/dummyImplementation.sol:DummyImplementation",
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Failed to verify Dummy Implementation");
|
||||
console.log(error);
|
||||
console.log();
|
||||
}
|
||||
try {
|
||||
await hre.run("verify:verify", {
|
||||
address: proxy.address,
|
||||
constructorArguments: [proxyAdmin, dummyImplementation.address],
|
||||
contract: "contracts/example/proxy.sol:Example",
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Failed to verify Proxy");
|
||||
console.log(error);
|
||||
console.log();
|
||||
}
|
||||
}
|
||||
|
||||
// We recommend this pattern to be able to use async/await everywhere
|
||||
// and properly handle errors.
|
||||
main().catch((error) => {
|
||||
console.error(error);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
12
tsconfig.json
Normal file
12
tsconfig.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2018",
|
||||
"module": "commonjs",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"outDir": "dist",
|
||||
"declaration": true
|
||||
},
|
||||
"include": ["./scripts", "./test", "./typechain"],
|
||||
"files": ["./hardhat.config.ts"]
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user