Skip to content

Commit

Permalink
hardcoded emergency withdraw address
Browse files Browse the repository at this point in the history
  • Loading branch information
pum committed Aug 26, 2023
1 parent db41764 commit 9b328ad
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 37 deletions.
2 changes: 1 addition & 1 deletion contracts/IVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ 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;
function emergencyWithdraw(uint256 amount_) external;

// must return keccak256("Vaults.Vault") ^ bytes32(uint256(uint160(address(VaultsFactory))))
function isVault() external view returns (bytes32);
Expand Down
1 change: 1 addition & 0 deletions contracts/IVaultsFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "./IVault.sol";
interface IVaultsFactory {
function feeReceiver() external view returns(address);
function feeBasisPoints() external view returns (uint256);
function emergencyWithdrawAddress() external view returns(address);

function unwrapDelay() external view returns (uint256);
function isPaused(IVault vault) external view returns (bool);
Expand Down
8 changes: 5 additions & 3 deletions contracts/VaultImplementation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -120,15 +120,17 @@ contract VaultImplementation is IVault, Initializable, ERC20PermitUpgradeable, R
emit UnwrapCancelled(msg.sender, amount);
}

function emergencyWithdraw(address to_, uint256 amount_) external nonReentrant {
function emergencyWithdraw(uint256 amount_) external nonReentrant {
require(factory.isPaused(this), "VAULTS: NOT_PAUSED");
require(msg.sender == address(factory), "VAULTS: NOT_FACTORY_ADDRESS");
require(to_ != address(0), "VAULTS: ZERO_ADDRESS");

address emergencyWithdrawAddress = factory.emergencyWithdrawAddress();
require(emergencyWithdrawAddress != address(0), "VAULTS: ZERO_ADDRESS");

emergency = true;

uint256 withdrawalAmount = (amount_ == 0) ? underlyingToken.balanceOf(address(this)) : amount_;
underlyingToken.safeTransfer(to_, withdrawalAmount);
underlyingToken.safeTransfer(emergencyWithdrawAddress, withdrawalAmount);
}

function _beforeTokenTransfer(address from, address /* to */, uint256 amount) internal view override {
Expand Down
14 changes: 11 additions & 3 deletions contracts/VaultsFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import "./IVault.sol";

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

address public defaultVaultImplementation;
uint256 public unwrapDelay;
Expand Down Expand Up @@ -44,8 +45,13 @@ contract VaultsFactory is IVaultsFactory, AccessControlEnumerable {
uint256 unwrapDelay_,
address rolesAddr_,
address initialFeeReceiver_,
uint256 initialFeeBasisPoints_
uint256 initialFeeBasisPoints_,
address emergencyWithdrawAddress_
) {
require(weth_ != address(0), "VAULTS: ZERO_ADDRESS");
require(vaultImplementationAddress_ != address(0), "VAULTS: ZERO_ADDRESS");
require(emergencyWithdrawAddress_ != address(0), "VAULTS: ZERO_ADDRESS");

weth = weth_;
defaultVaultImplementation = vaultImplementationAddress_;
unwrapDelay = unwrapDelay_;
Expand All @@ -56,6 +62,8 @@ contract VaultsFactory is IVaultsFactory, AccessControlEnumerable {

_setFeeReceiver(initialFeeReceiver_);
_setFeeBasisPoints(initialFeeBasisPoints_);

emergencyWithdrawAddress = emergencyWithdrawAddress_;
}

function deployVault(address underlyingToken_, string memory name_, string memory symbol_) external onlyRole(TEAM_ROLE) returns (IVault result) {
Expand Down Expand Up @@ -108,8 +116,8 @@ contract VaultsFactory is IVaultsFactory, AccessControlEnumerable {
defaultVaultImplementation = vaultImplementation_;
}

function emergencyWithdrawFromVault(IVault vaultAddress_, address to_, uint256 amount_) external onlyRole(DEFAULT_ADMIN_ROLE) {
vaultAddress_.emergencyWithdraw(to_, amount_);
function emergencyWithdrawFromVault(IVault vaultAddress_, uint256 amount_) external onlyRole(DEFAULT_ADMIN_ROLE) {
vaultAddress_.emergencyWithdraw(amount_);
}

function _setFeeReceiver(address feeReceiver_) internal {
Expand Down
45 changes: 21 additions & 24 deletions test/Vault.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const ETH = (x) => ethers.utils.parseEther(x.toString())

describe("Vault", function () {
let vaultsFactory, vaultImplementation, weth;
let deployer, adminRole, pauseRole, teamRole, feeReceiver, nobody, addr1, addr2;
let deployer, adminRole, pauseRole, teamRole, feeReceiver, nobody, addr1, addr2, emergencyWithdrawAddress;

let token1, token2, token3;
let vault1, vault2, vault3, wethVault;
Expand All @@ -18,7 +18,7 @@ describe("Vault", function () {
let PAUSE_ROLE, TEAM_ROLE, ADMIN_ROLE;

beforeEach(async function () {
[deployer, adminRole, pauseRole, teamRole, feeReceiver, nobody, addr1, addr2] = await ethers.getSigners();
[deployer, adminRole, pauseRole, teamRole, feeReceiver, nobody, addr1, addr2, emergencyWithdrawAddress] = await ethers.getSigners();

const WethFactory = await ethers.getContractFactory("MockWeth");
const TokenFactory = await ethers.getContractFactory("MockERC20");
Expand All @@ -38,7 +38,7 @@ describe("Vault", function () {
vaultImplementation = await VaultImplementationFactory.deploy();
await vaultImplementation.deployed();

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

PAUSE_ROLE = await vaultsFactory.PAUSE_ROLE();
Expand Down Expand Up @@ -423,34 +423,31 @@ 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("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);
await expect(vaultsFactory.connect(pauseRole).emergencyWithdrawFromVault(vault1.address, nobody.address, ETH(1))).to.be.revertedWith("AccessControl: account " + pauseRole.address.toLowerCase() + " is missing role " + ADMIN_ROLE);
await expect(vaultsFactory.connect(teamRole).emergencyWithdrawFromVault(vault1.address, nobody.address, ETH(1))).to.be.revertedWith("AccessControl: account " + teamRole.address.toLowerCase() + " is missing role " + ADMIN_ROLE);
await expect(vaultsFactory.connect(adminRole).emergencyWithdrawFromVault(vault1.address, nobody.address, ETH(1))).to.be.revertedWith("VAULTS: NOT_PAUSED");
await expect(vault1.connect(adminRole).emergencyWithdraw(ETH(1))).to.be.revertedWith("TransparentUpgradeableProxy: admin cannot fallback to proxy target");
await expect(vault1.connect(addr1).emergencyWithdraw(ETH(1))).to.be.revertedWith("VAULTS: NOT_PAUSED");
await expect(vault1.connect(nobody).emergencyWithdraw(ETH(1))).to.be.revertedWith("VAULTS: NOT_PAUSED");
await expect(vaultsFactory.connect(nobody).emergencyWithdrawFromVault(vault1.address, ETH(1))).to.be.revertedWith("AccessControl: account " + nobody.address.toLowerCase() + " is missing role " + ADMIN_ROLE);
await expect(vaultsFactory.connect(pauseRole).emergencyWithdrawFromVault(vault1.address, ETH(1))).to.be.revertedWith("AccessControl: account " + pauseRole.address.toLowerCase() + " is missing role " + ADMIN_ROLE);
await expect(vaultsFactory.connect(teamRole).emergencyWithdrawFromVault(vault1.address, ETH(1))).to.be.revertedWith("AccessControl: account " + teamRole.address.toLowerCase() + " is missing role " + ADMIN_ROLE);
await expect(vaultsFactory.connect(adminRole).emergencyWithdrawFromVault(vault1.address, ETH(1))).to.be.revertedWith("VAULTS: NOT_PAUSED");


await vaultsFactory.connect(adminRole).pauseAllVaults()

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);
await expect(vaultsFactory.connect(pauseRole).emergencyWithdrawFromVault(vault1.address, nobody.address, ETH(1))).to.be.revertedWith("AccessControl: account " + pauseRole.address.toLowerCase() + " is missing role " + ADMIN_ROLE);
await expect(vaultsFactory.connect(teamRole).emergencyWithdrawFromVault(vault1.address, nobody.address, ETH(1))).to.be.revertedWith("AccessControl: account " + teamRole.address.toLowerCase() + " is missing role " + ADMIN_ROLE);
await expect(vault1.connect(adminRole).emergencyWithdraw(ETH(1))).to.be.revertedWith("TransparentUpgradeableProxy: admin cannot fallback to proxy target");
await expect(vault1.connect(addr1).emergencyWithdraw(ETH(1))).to.be.revertedWith("VAULTS: NOT_FACTORY_ADDRESS");
await expect(vault1.connect(nobody).emergencyWithdraw(ETH(1))).to.be.revertedWith("VAULTS: NOT_FACTORY_ADDRESS");
await expect(vaultsFactory.connect(nobody).emergencyWithdrawFromVault(vault1.address, ETH(1))).to.be.revertedWith("AccessControl: account " + nobody.address.toLowerCase() + " is missing role " + ADMIN_ROLE);
await expect(vaultsFactory.connect(pauseRole).emergencyWithdrawFromVault(vault1.address, ETH(1))).to.be.revertedWith("AccessControl: account " + pauseRole.address.toLowerCase() + " is missing role " + ADMIN_ROLE);
await expect(vaultsFactory.connect(teamRole).emergencyWithdrawFromVault(vault1.address, ETH(1))).to.be.revertedWith("AccessControl: account " + teamRole.address.toLowerCase() + " is missing role " + ADMIN_ROLE);

expect(await token1.balanceOf(emergencyWithdrawAddress.address)).to.equal(0);

await expect(vaultsFactory.connect(adminRole).emergencyWithdrawFromVault(vault1.address, ZERO_ADDRESS, ETH(1))).to.be.revertedWith("VAULTS: ZERO_ADDRESS");
await vaultsFactory.connect(adminRole).emergencyWithdrawFromVault(vault1.address, ETH('0.3'));
expect(await token1.balanceOf(emergencyWithdrawAddress.address)).to.equal(ETH('0.3'));

expect(await token1.balanceOf(nobody.address)).to.equal(0);

await vaultsFactory.connect(adminRole).emergencyWithdrawFromVault(vault1.address, nobody.address, ETH('0.3'));
expect(await token1.balanceOf(nobody.address)).to.equal(ETH('0.3'));

await vaultsFactory.connect(adminRole).emergencyWithdrawFromVault(vault1.address, nobody.address, 0);
expect(await token1.balanceOf(nobody.address)).to.equal(ETH('1'));
await vaultsFactory.connect(adminRole).emergencyWithdrawFromVault(vault1.address, 0);
expect(await token1.balanceOf(emergencyWithdrawAddress.address)).to.equal(ETH('1'));

await vaultsFactory.connect(adminRole).unpauseAllVaults()

Expand Down
26 changes: 20 additions & 6 deletions test/VaultsFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const ETH = ethers.utils.parseEther

describe("VaultsFactory", function () {
let vaultsFactory, vaultImplementation, weth;
let deployer, adminRole, pauseRole, teamRole, feeReceiver, nobody, addr1, addr2;
let deployer, adminRole, pauseRole, teamRole, feeReceiver, nobody, addr1, addr2, emergencyWithdrawAddress;

let token1, token2, token3;

Expand All @@ -15,7 +15,7 @@ describe("VaultsFactory", function () {
let PAUSE_ROLE, TEAM_ROLE, ADMIN_ROLE;

beforeEach(async function () {
[deployer, adminRole, pauseRole, teamRole, feeReceiver, nobody, addr1, addr2] = await ethers.getSigners();
[deployer, adminRole, pauseRole, teamRole, feeReceiver, nobody, addr1, addr2, emergencyWithdrawAddress] = await ethers.getSigners();

const WethFactory = await ethers.getContractFactory("MockWeth");
const TokenFactory = await ethers.getContractFactory("MockERC20");
Expand All @@ -35,7 +35,7 @@ describe("VaultsFactory", function () {
vaultImplementation = await VaultImplementationFactory.deploy();
await vaultImplementation.deployed();

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

PAUSE_ROLE = await vaultsFactory.PAUSE_ROLE();
Expand All @@ -49,7 +49,7 @@ describe("VaultsFactory", function () {

it("constructor initializes state correctly", async function () {

const newVaultsFactory = await VaultsFactoryFactory.deploy(weth.address, vaultImplementation.address, 3600, adminRole.address, ZERO_ADDRESS, 0);
const newVaultsFactory = await VaultsFactoryFactory.deploy(weth.address, vaultImplementation.address, 3600, adminRole.address, ZERO_ADDRESS, 0, emergencyWithdrawAddress.address);
await newVaultsFactory.deployed();

expect(await newVaultsFactory.weth()).to.equal(weth.address);
Expand All @@ -70,12 +70,26 @@ describe("VaultsFactory", function () {

expect(await newVaultsFactory.feeReceiver()).to.equal(ZERO_ADDRESS);
expect(await newVaultsFactory.feeBasisPoints()).to.equal(0);

expect(await newVaultsFactory.emergencyWithdrawAddress()).to.equal(emergencyWithdrawAddress.address);
});


it("constructor negative", async function () {

await expect(VaultsFactoryFactory.deploy(ZERO_ADDRESS, vaultImplementation.address, 3600, adminRole.address, ZERO_ADDRESS, 0, emergencyWithdrawAddress.address))
.to.be.revertedWith("VAULTS: ZERO_ADDRESS");
await expect(VaultsFactoryFactory.deploy(weth.address, ZERO_ADDRESS, 3600, adminRole.address, ZERO_ADDRESS, 0, emergencyWithdrawAddress.address))
.to.be.revertedWith("VAULTS: ZERO_ADDRESS");
await expect(VaultsFactoryFactory.deploy(weth.address, vaultImplementation.address, 3600, adminRole.address, ZERO_ADDRESS, 0, ZERO_ADDRESS))
.to.be.revertedWith("VAULTS: ZERO_ADDRESS");

});

it("constructor initializes state with non-zero fee receiver and fee correctly", async function () {
const fee = 5; // assuming this is in basis points, which means 0.05%

const newVaultsFactory = await VaultsFactoryFactory.deploy(weth.address, vaultImplementation.address, 3600, adminRole.address, feeReceiver.address, fee);
const newVaultsFactory = await VaultsFactoryFactory.deploy(weth.address, vaultImplementation.address, 3600, adminRole.address, feeReceiver.address, fee, emergencyWithdrawAddress.address);
await newVaultsFactory.deployed();

// Check basic properties
Expand All @@ -94,7 +108,7 @@ describe("VaultsFactory", function () {
});

it("smoke roles checks", async function () {
const newVaultsFactory = await VaultsFactoryFactory.deploy(weth.address, vaultImplementation.address, 3600, adminRole.address, ZERO_ADDRESS, 0);
const newVaultsFactory = await VaultsFactoryFactory.deploy(weth.address, vaultImplementation.address, 3600, adminRole.address, ZERO_ADDRESS, 0, emergencyWithdrawAddress.address);
await newVaultsFactory.deployed();

// ADMIN_ROLE is admin for pause role
Expand Down

0 comments on commit 9b328ad

Please sign in to comment.