Skip to content

Commit

Permalink
proxy returned
Browse files Browse the repository at this point in the history
  • Loading branch information
pum committed Aug 19, 2023
1 parent 7a684af commit 9b996e6
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 54 deletions.
1 change: 1 addition & 0 deletions contracts/IVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity ^0.8.0;
import "./IVaultsFactory.sol";

interface IVault {
function initialize(address underlyingToken_, IVaultsFactory factory_, bool isEth, string memory name_, string memory symbol_) external;
function emergencyWithdraw(address to_, uint256 amount_) external;

// must return keccak256("Vaults.Vault") ^ bytes32(uint256(uint160(address(VaultsFactory))))
Expand Down
26 changes: 13 additions & 13 deletions contracts/Vault.sol → contracts/VaultImplementation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import "./IVaultsFactory.sol";
import "./IVault.sol";
import "./IWETH.sol";


contract Vault is IVault, ERC20Permit, ReentrancyGuard {
using SafeERC20 for IERC20Metadata;
contract VaultImplementation is IVault, Initializable, ERC20PermitUpgradeable, ReentrancyGuardUpgradeable {
using SafeERC20Upgradeable for IERC20MetadataUpgradeable;

IERC20Metadata public immutable underlyingToken;
IVaultsFactory public immutable factory;
bool public immutable isEth;
IERC20MetadataUpgradeable public underlyingToken;
IVaultsFactory public factory;
bool public isEth;

bool public emergency = false;

Expand All @@ -37,13 +37,13 @@ contract Vault is IVault, ERC20Permit, ReentrancyGuard {
_;
}

constructor(IERC20Metadata underlyingToken_, IVaultsFactory factory_, bool isEth_, string memory name_, string memory symbol_)
ERC20Permit(name_)
ERC20(name_, symbol_)
{
underlyingToken = underlyingToken_;
function initialize(address underlyingToken_, IVaultsFactory factory_, bool isEth_, string memory name_, string memory symbol_) public initializer {
underlyingToken = IERC20MetadataUpgradeable(underlyingToken_);
factory = factory_;
isEth = isEth_;
__ERC20_init(name_, symbol_);
__ERC20Permit_init(name_);
__ReentrancyGuard_init();
}

// only accept ETH via fallback from the WETH contract
Expand Down
29 changes: 22 additions & 7 deletions contracts/VaultsFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/AccessControlEnumerable.sol";
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "./IVaultsFactory.sol";
import "./Vault.sol";
import "./IVault.sol";

contract VaultsFactory is IVaultsFactory, AccessControlEnumerable {
address public immutable weth;

address public vaultsImplementation;
uint256 public unwrapDelay;

address public feeReceiver;
Expand Down Expand Up @@ -38,12 +41,14 @@ contract VaultsFactory is IVaultsFactory, AccessControlEnumerable {

constructor(
address weth_,
address vaultsImplementationAddress_,
uint256 unwrapDelay_,
address rolesAddr_,
address initialFeeReceiver_,
uint256 initialFeeBasisPoints_
) {
weth = weth_;
vaultsImplementation = vaultsImplementationAddress_;
unwrapDelay = unwrapDelay_;

_setupRole(DEFAULT_ADMIN_ROLE, rolesAddr_);
Expand All @@ -55,15 +60,20 @@ contract VaultsFactory is IVaultsFactory, AccessControlEnumerable {
_setFeeBasisPoints(initialFeeBasisPoints_);
}

function deployVault(IERC20Metadata underlyingToken_, string memory name_, string memory symbol_) external onlyRole(DEPLOY_ROLE) returns (IVault result) {
result = new Vault(
function deployVault(address underlyingToken_, string memory name_, string memory symbol_) external onlyRole(DEPLOY_ROLE) returns (IVault result) {
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
vaultsImplementation,
getRoleMember(DEFAULT_ADMIN_ROLE, 0),
""
);
result = IVault(address(proxy));
result.initialize(
underlyingToken_,
this,
address(underlyingToken_) == weth,
bytes(name_).length != 0 ? name_ : string(abi.encodePacked("Vaulted ", underlyingToken_.symbol())),
bytes(symbol_).length != 0 ? symbol_ : string(abi.encodePacked("v", underlyingToken_.symbol()))
underlyingToken_ == weth,
bytes(name_).length != 0 ? name_ : string(abi.encodePacked("Vaulted ", IERC20Metadata(underlyingToken_).symbol())),
bytes(symbol_).length != 0 ? symbol_ : string(abi.encodePacked("v", IERC20Metadata(underlyingToken_).symbol()))
);

emit VaultDeployed(result);
}

Expand Down Expand Up @@ -95,6 +105,11 @@ contract VaultsFactory is IVaultsFactory, AccessControlEnumerable {
unwrapDelay = unwrapDelay_;
}

function setVaultsImplementation(address vaultsImplementation_) external onlyRole(DEFAULT_ADMIN_ROLE) {
require(vaultsImplementation_ != address(0), "VAULTS: ZERO_ADDRESS");
vaultsImplementation = vaultsImplementation_;
}

function emergencyWithdrawFromVault(IVault vaultAddress_, address to_, uint256 amount_) external onlyRole(DEFAULT_ADMIN_ROLE) {
vaultAddress_.emergencyWithdraw(to_, amount_);
}
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"@nomiclabs/hardhat-ethers": "^2.2.3",
"@nomiclabs/hardhat-waffle": "^2.0.6",
"@openzeppelin/contracts": "^4.9.3",
"@openzeppelin/contracts-upgradeable": "^4.9.3",
"chai": "^4.3.7",
"ethereum-waffle": "^4.0.10",
"ethers": "^5.0.0",
Expand Down
13 changes: 12 additions & 1 deletion scripts/deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,28 @@ async function main() {
const FEE_RECEIVER = '0x0000000000000000000000000000000000000000';
const FEE = 0;

const VaultFactory = await ethers.getContractFactory("VaultImplementation");
const vault = await VaultFactory.deploy();
await vault.deployed();
console.log("VaultImplementation: ", vault.address);

const VaultsFactoryFactory = await ethers.getContractFactory("VaultsFactory");
const factory = await VaultsFactoryFactory.deploy(WETH, DELAY, ADMIN, FEE_RECEIVER, FEE);
const factory = await VaultsFactoryFactory.deploy(WETH, vault.address, DELAY, ADMIN, FEE_RECEIVER, FEE);
await factory.deployed();
console.log("Factory: ", factory.address);

await new Promise(r => setTimeout(r, 100000)); // time to index new contracts

await hre.run("verify:verify", {
address: vault.address,
constructorArguments: [],
});

await hre.run("verify:verify", {
address: factory.address,
constructorArguments: [WETH, vault.address, DELAY, ADMIN, FEE_RECEIVER, FEE],
});

}

// We recommend this pattern to be able to use async/await everywhere
Expand Down
83 changes: 58 additions & 25 deletions test/Vault.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const BN = ethers.BigNumber.from;
const ETH = (x) => ethers.utils.parseEther(x.toString())

describe("Vault", function () {
let vaultsFactory, weth;
let vaultsFactory, vaultsImplementation, weth;
let deployer, adminRole, pauseRole, unpauseRole, deployRole, feeReceiver, nobody, addr1, addr2;

let token1, token2, token3;
Expand All @@ -22,6 +22,7 @@ describe("Vault", function () {

const WethFactory = await ethers.getContractFactory("MockWeth");
const TokenFactory = await ethers.getContractFactory("MockERC20");
const VaultImplementationFactory = await ethers.getContractFactory("VaultImplementation");
VaultsFactoryFactory = await ethers.getContractFactory("VaultsFactory");

weth = await WethFactory.deploy();
Expand All @@ -34,7 +35,10 @@ describe("Vault", function () {
token3 = await TokenFactory.deploy("Mock Token3", "MTK3", 33, ETH("1000"));
await token3.deployed();

vaultsFactory = await VaultsFactoryFactory.deploy(weth.address, 3600, adminRole.address, ZERO_ADDRESS, 0);
vaultsImplementation = await VaultImplementationFactory.deploy();
await vaultsImplementation.deployed();

vaultsFactory = await VaultsFactoryFactory.deploy(weth.address, vaultsImplementation.address, 3600, adminRole.address, ZERO_ADDRESS, 0);
await vaultsFactory.deployed();

PAUSE_ROLE = await vaultsFactory.PAUSE_ROLE();
Expand All @@ -48,33 +52,37 @@ describe("Vault", function () {
await vaultsFactory.connect(adminRole).grantRole(DEPLOY_ROLE, deployRole.address);

let tx = await vaultsFactory.connect(deployRole).deployVault(token1.address, "", "");
const vault1addr = (await tx.wait()).events[0].args.vaultAddress;
vault1 = await ethers.getContractAt('Vault', vault1addr)
const vault1addr = (await tx.wait()).events[3].args.vaultAddress;
vault1 = await ethers.getContractAt('VaultImplementation', vault1addr)

tx = await vaultsFactory.connect(deployRole).deployVault(token1.address, "", "");
const vault2addr = (await tx.wait()).events[0].args.vaultAddress;
vault2 = await ethers.getContractAt('Vault', vault2addr)
const vault2addr = (await tx.wait()).events[3].args.vaultAddress;
vault2 = await ethers.getContractAt('VaultImplementation', vault2addr)

tx = await vaultsFactory.connect(deployRole).deployVault(token1.address, "", "");
const vault3addr = (await tx.wait()).events[0].args.vaultAddress;
vault3 = await ethers.getContractAt('Vault', vault3addr)
const vault3addr = (await tx.wait()).events[3].args.vaultAddress;
vault3 = await ethers.getContractAt('VaultImplementation', vault3addr)

tx = await vaultsFactory.connect(deployRole).deployVault(weth.address, "", "");
const wethVaultAddr = (await tx.wait()).events[0].args.vaultAddress;
wethVault = await ethers.getContractAt('Vault', wethVaultAddr)
const wethVaultAddr = (await tx.wait()).events[3].args.vaultAddress;
wethVault = await ethers.getContractAt('VaultImplementation', wethVaultAddr)
});

it("simple deploys and check params", async function () {
const vault1proxy = await ethers.getContractAt('ITransparentUpgradeableProxy', vault1.address)

expect(await vault1.name()).to.equal('Vaulted MTK1')
expect(await vault1.symbol()).to.equal('vMTK1')
expect(await vault1.decimals()).to.equal(0)
expect(await vault1.isEth()).to.equal(false)
expect(await vault1.factory()).to.equal(vaultsFactory.address)
expect(await vault1.underlyingToken()).to.equal(token1.address)
expect(await vault1proxy.connect(adminRole).admin()).to.equal(adminRole.address)


let tx = await vaultsFactory.connect(deployRole).deployVault(weth.address, "name", "symbol");
const vaultAddr = (await tx.wait()).events[0].args.vaultAddress;
const vault = await ethers.getContractAt('Vault', vaultAddr)
const vaultAddr = (await tx.wait()).events[3].args.vaultAddress;
const vault = await ethers.getContractAt('VaultImplementation', vaultAddr)

expect(await vault.name()).to.equal('name')
expect(await vault.symbol()).to.equal('symbol')
Expand All @@ -84,6 +92,13 @@ describe("Vault", function () {
expect(await vault.underlyingToken()).to.equal(weth.address)
});

it("reinitialization prohibited", async function () {
await expect(vault1.connect(adminRole).initialize(token2.address, vaultsFactory.address, true, "sd", "sd")).to.be.revertedWith("TransparentUpgradeableProxy: admin cannot fallback to proxy target");
await expect(vault1.connect(nobody).initialize(token2.address, vaultsFactory.address, true, "sd", "sd")).to.be.revertedWith("Initializable: contract is already initialized");
await expect(vault1.initialize(token2.address, vaultsFactory.address, true, "sd", "sd")).to.be.revertedWith("Initializable: contract is already initialized");
});


it("ether methods for non ether", async function () {
await expect(vault1.wrapEther()).to.be.revertedWith("VAULTS: NOT_ETHER");

Expand Down Expand Up @@ -410,7 +425,7 @@ describe("Vault", function () {
await token1.approve(vault1.address, ETH(1));
await vault1.wrap(ETH(1))

await expect(vault1.connect(adminRole).emergencyWithdraw(nobody.address, ETH(1))).to.be.revertedWith("VAULTS: NOT_PAUSED");
await expect(vault1.connect(adminRole).emergencyWithdraw(nobody.address, ETH(1))).to.be.revertedWith("TransparentUpgradeableProxy: admin cannot fallback to proxy target");
await expect(vault1.connect(addr1).emergencyWithdraw(nobody.address, ETH(1))).to.be.revertedWith("VAULTS: NOT_PAUSED");
await expect(vault1.connect(nobody).emergencyWithdraw(nobody.address, ETH(1))).to.be.revertedWith("VAULTS: NOT_PAUSED");
await expect(vaultsFactory.connect(nobody).emergencyWithdrawFromVault(vault1.address, nobody.address, ETH(1))).to.be.revertedWith("AccessControl: account " + nobody.address.toLowerCase() + " is missing role " + ADMIN_ROLE);
Expand All @@ -422,7 +437,7 @@ describe("Vault", function () {

await vaultsFactory.connect(adminRole).pauseAllVaults()

await expect(vault1.connect(adminRole).emergencyWithdraw(nobody.address, ETH(1))).to.be.revertedWith("VAULTS: NOT_FACTORY_ADDRESS");
await expect(vault1.connect(adminRole).emergencyWithdraw(nobody.address, ETH(1))).to.be.revertedWith("TransparentUpgradeableProxy: admin cannot fallback to proxy target");
await expect(vault1.connect(addr1).emergencyWithdraw(nobody.address, ETH(1))).to.be.revertedWith("VAULTS: NOT_FACTORY_ADDRESS");
await expect(vault1.connect(nobody).emergencyWithdraw(nobody.address, ETH(1))).to.be.revertedWith("VAULTS: NOT_FACTORY_ADDRESS");
await expect(vaultsFactory.connect(nobody).emergencyWithdrawFromVault(vault1.address, nobody.address, ETH(1))).to.be.revertedWith("AccessControl: account " + nobody.address.toLowerCase() + " is missing role " + ADMIN_ROLE);
Expand Down Expand Up @@ -453,24 +468,42 @@ describe("Vault", function () {
it("simple permit check", async function () {
await token1.approve(vault1.address, ETH(1));
await vault1.wrap(ETH(1))
const deadline = BN((await ethers.provider.getBlock()).timestamp + 60);
const chainId = (await ethers.provider.getNetwork()).chainId;

expect(await vault1.balanceOf(deployer.address)).to.equal(ETH(1))

await expect(vault1.connect(addr1).transferFrom(deployer.address, nobody.address, 333)).to.be.revertedWith("ERC20: insufficient allowance");
await expect(vault1.connect(addr2).transferFrom(deployer.address, nobody.address, 333)).to.be.revertedWith("ERC20: insufficient allowance");

const deadline = BN((await ethers.provider.getBlock()).timestamp + 60);
const signature = await getSignature(
"Vaulted MTK1",
(await ethers.provider.getNetwork()).chainId,
vault1.address,
deployer,
addr1.address,
BN(333),
BN(0),
deadline
);

let signature = await getSignature("Vaulted MTK2", chainId, vault1.address, deployer, addr1.address, BN(333), BN(0), deadline);
await expect( vault1.connect(nobody).permit(deployer.address, addr1.address, BN(333), deadline, signature[0], signature[1], signature[2])).to.be.revertedWith("ERC20Permit: invalid signature");

signature = await getSignature("Vaulted MTK1", 3, vault1.address, deployer, addr1.address, BN(333), BN(0), deadline);
await expect( vault1.connect(nobody).permit(deployer.address, addr1.address, BN(333), deadline, signature[0], signature[1], signature[2])).to.be.revertedWith("ERC20Permit: invalid signature");

signature = await getSignature("Vaulted MTK1", chainId, vault2.address, deployer, addr1.address, BN(333), BN(0), deadline);
await expect( vault1.connect(nobody).permit(deployer.address, addr1.address, BN(333), deadline, signature[0], signature[1], signature[2])).to.be.revertedWith("ERC20Permit: invalid signature");

signature = await getSignature("Vaulted MTK1", chainId, vault1.address, addr2, addr1.address, BN(333), BN(0), deadline);
await expect( vault1.connect(nobody).permit(deployer.address, addr1.address, BN(333), deadline, signature[0], signature[1], signature[2])).to.be.revertedWith("ERC20Permit: invalid signature");

signature = await getSignature("Vaulted MTK1", chainId, vault1.address, deployer, addr2.address, BN(333), BN(0), deadline);
await expect( vault1.connect(nobody).permit(deployer.address, addr1.address, BN(333), deadline, signature[0], signature[1], signature[2])).to.be.revertedWith("ERC20Permit: invalid signature");

signature = await getSignature("Vaulted MTK1", chainId, vault1.address, deployer, addr1.address, BN(333), BN(1), deadline);
await expect( vault1.connect(nobody).permit(deployer.address, addr1.address, BN(333), deadline, signature[0], signature[1], signature[2])).to.be.revertedWith("ERC20Permit: invalid signature");

signature = await getSignature("Vaulted MTK1", chainId, vault1.address, deployer, addr1.address, BN(333), BN(0), 0);
await expect( vault1.connect(nobody).permit(deployer.address, addr1.address, BN(333), deadline, signature[0], signature[1], signature[2])).to.be.revertedWith("ERC20Permit: invalid signature");


await expect(vault1.connect(addr1).transferFrom(deployer.address, nobody.address, 333)).to.be.revertedWith("ERC20: insufficient allowance");
await expect(vault1.connect(addr2).transferFrom(deployer.address, nobody.address, 333)).to.be.revertedWith("ERC20: insufficient allowance");


signature = await getSignature("Vaulted MTK1", chainId, vault1.address, deployer, addr1.address, BN(333), BN(0), deadline);
await vault1.connect(nobody).permit(deployer.address, addr1.address, BN(333), deadline, signature[0], signature[1], signature[2])

await expect(vault1.connect(addr1).transferFrom(deployer.address, nobody.address, 334)).to.be.revertedWith("ERC20: insufficient allowance");
Expand Down

0 comments on commit 9b996e6

Please sign in to comment.