Skip to content

Commit

Permalink
Add filters for Gro
Browse files Browse the repository at this point in the history
  • Loading branch information
olivdb committed May 26, 2021
1 parent 6635372 commit a61465f
Show file tree
Hide file tree
Showing 5 changed files with 359 additions and 0 deletions.
45 changes: 45 additions & 0 deletions contracts/infrastructure/dapp/GroDepositFilter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (C) 2021 Argent Labs Ltd. <https://argent.xyz>

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.3;

import "./BaseFilter.sol";

/**
* @title GroDepositFilter
* @notice Filter used for deposits to Gro Protocol
* @author Olivier VDB - <olivier@argent.xyz>
*/
contract GroDepositFilter is BaseFilter {

bytes4 private constant DEPOSIT1 = bytes4(keccak256("depositPwrd(uint256[],uint256,address)"));
bytes4 private constant DEPOSIT2 = bytes4(keccak256("depositGvt(uint256[],uint256,address)"));
bytes4 private constant ERC20_APPROVE = bytes4(keccak256("approve(address,uint256)"));

function isValid(address /*_wallet*/, address _spender, address _to, bytes calldata _data) external view override returns (bool valid) {
// disable ETH transfer
if (_data.length < 4) {
return false;
}

bytes4 methodId = getMethod(_data);
if(_spender == _to) {
return (methodId == DEPOSIT1 || methodId == DEPOSIT2);
} else {
return (methodId == ERC20_APPROVE);
}
}
}
42 changes: 42 additions & 0 deletions contracts/infrastructure/dapp/GroWithdrawFilter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (C) 2021 Argent Labs Ltd. <https://argent.xyz>

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.3;

import "./BaseFilter.sol";

/**
* @title GroWithdrawFilter
* @notice Filter used for withdrawals from Gro Protocol
* @author Olivier VDB - <olivier@argent.xyz>
*/
contract GroWithdrawFilter is BaseFilter {

bytes4 private constant WITHDRAW1 = bytes4(keccak256("withdrawByStablecoin(bool,uint256,uint256,uint256)"));
bytes4 private constant WITHDRAW2 = bytes4(keccak256("withdrawByLPToken(bool,uint256,uint256[])"));
bytes4 private constant WITHDRAW_ALL1 = bytes4(keccak256("withdrawAllSingle(bool,uint256,uint256)"));
bytes4 private constant WITHDRAW_ALL2 = bytes4(keccak256("withdrawAllBalanced(bool,uint256[])"));

function isValid(address /*_wallet*/, address _spender, address _to, bytes calldata _data) external view override returns (bool valid) {
// disable ETH transfer
if (_data.length < 4) {
return false;
}

bytes4 methodId = getMethod(_data);
return (_spender == _to && (methodId == WITHDRAW1 || methodId == WITHDRAW_ALL1 || methodId == WITHDRAW2 || methodId == WITHDRAW_ALL2));
}
}
18 changes: 18 additions & 0 deletions lib_0.7/gro/DepositHandlerMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
pragma solidity >=0.6.0 <0.8.0;

contract DepositHandlerMock {

function referral(address referee) external view returns (address) {}

function depositGvt(
uint256[] calldata inAmounts,
uint256 minAmount,
address referral
) external {}

function depositPwrd(
uint256[] calldata inAmounts,
uint256 minAmount,
address referral
) external {}
}
27 changes: 27 additions & 0 deletions lib_0.7/gro/WithdrawHandlerMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
pragma solidity >=0.6.0 <0.8.0;

contract WithdrawHandlerMock {

function withdrawalFee(bool pwrd) external view returns (uint256) {}

function withdrawByLPToken(
bool pwrd,
uint256 lpAmount,
uint256[] calldata minAmounts
) external {}

function withdrawByStablecoin(
bool pwrd,
uint256 index,
uint256 lpAmount,
uint256 minAmount
) external {}

function withdrawAllSingle(
bool pwrd,
uint256 index,
uint256 minAmount
) external {}

function withdrawAllBalanced(bool pwrd, uint256[] calldata minAmounts) external {}
}
227 changes: 227 additions & 0 deletions test/gro.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
/* global artifacts */

const ethers = require("ethers");
const chai = require("chai");
const BN = require("bn.js");
const bnChai = require("bn-chai");

const { assert } = chai;
chai.use(bnChai(BN));

// UniswapV2
const UniswapV2Factory = artifacts.require("UniswapV2FactoryMock");
const UniswapV2Router01 = artifacts.require("UniswapV2Router01Mock");
const WETH = artifacts.require("WETH9");

// Gro
const DepositHandler = artifacts.require("DepositHandlerMock");
const WithdrawHandler = artifacts.require("WithdrawHandlerMock");
const ERC20 = artifacts.require("TestERC20");

// Argent
const WalletFactory = artifacts.require("WalletFactory");
const BaseWallet = artifacts.require("BaseWallet");
const Registry = artifacts.require("ModuleRegistry");
const TransferStorage = artifacts.require("TransferStorage");
const GuardianStorage = artifacts.require("GuardianStorage");
const ArgentModule = artifacts.require("ArgentModule");
const DappRegistry = artifacts.require("DappRegistry");
const DepositFilter = artifacts.require("GroDepositFilter");
const WithdrawFilter = artifacts.require("GroWithdrawFilter");

// Utils
const utils = require("../utils/utilities.js");
const { ETH_TOKEN, initNonce, encodeCalls, encodeTransaction } = require("../utils/utilities.js");

const ZERO_ADDRESS = ethers.constants.AddressZero;
const SECURITY_PERIOD = 2;
const SECURITY_WINDOW = 2;
const LOCK_PERIOD = 4;
const RECOVERY_PERIOD = 4;
const AMOUNT = web3.utils.toWei("0.01");

const RelayManager = require("../utils/relay-manager");

contract("yEarn Filter", (accounts) => {
let manager;

const infrastructure = accounts[0];
const owner = accounts[1];
const guardian1 = accounts[2];
const relayer = accounts[4];
const refundAddress = accounts[7];

let registry;
let transferStorage;
let guardianStorage;
let module;
let wallet;
let factory;
let dappRegistry;

let uniswapRouter;

let weth;
let tokenA;

let depositHandler;
let withdrawHandler;

before(async () => {
// Deploy test token
weth = await WETH.new();
tokenA = await ERC20.new([infrastructure], web3.utils.toWei("1000"), 18);

// Deploy Gro
depositHandler = await DepositHandler.new();
withdrawHandler = await WithdrawHandler.new();

// Deploy and fund UniswapV2
const uniswapFactory = await UniswapV2Factory.new(ZERO_ADDRESS);
uniswapRouter = await UniswapV2Router01.new(uniswapFactory.address, weth.address);

// deploy Argent
registry = await Registry.new();
dappRegistry = await DappRegistry.new(0);
guardianStorage = await GuardianStorage.new();
transferStorage = await TransferStorage.new();
module = await ArgentModule.new(
registry.address,
guardianStorage.address,
transferStorage.address,
dappRegistry.address,
uniswapRouter.address,
SECURITY_PERIOD,
SECURITY_WINDOW,
RECOVERY_PERIOD,
LOCK_PERIOD);
await registry.registerModule(module.address, ethers.utils.formatBytes32String("ArgentModule"));
const depositFilter = await DepositFilter.new();
const withdrawFilter = await WithdrawFilter.new();

await dappRegistry.addDapp(0, depositHandler.address, depositFilter.address);
await dappRegistry.addDapp(0, withdrawHandler.address, withdrawFilter.address);
await dappRegistry.addDapp(0, relayer, ZERO_ADDRESS);

const walletImplementation = await BaseWallet.new();
factory = await WalletFactory.new(
walletImplementation.address,
guardianStorage.address,
refundAddress);
await factory.addManager(infrastructure);
manager = new RelayManager(guardianStorage.address, ZERO_ADDRESS);
});

beforeEach(async () => {
// create wallet
const walletAddress = await utils.createWallet(factory.address, owner, [module.address], guardian1);
wallet = await BaseWallet.at(walletAddress);

// fund wallet
await wallet.send(web3.utils.toWei("1"));
await tokenA.mint(wallet.address, web3.utils.toWei("1000"));

await initNonce(wallet, module, manager, SECURITY_PERIOD);
});

const multiCall = async (transactions) => {
const txReceipt = await manager.relay(
module,
"multiCall",
[wallet.address, transactions],
wallet,
[owner],
1,
ETH_TOKEN,
relayer);
return utils.parseRelayReceipt(txReceipt);
};

const depositGvt = async () => multiCall(encodeCalls([
[tokenA, "approve", [depositHandler.address, AMOUNT]],
[depositHandler, "depositGvt", [[AMOUNT, 0, 0], 1, ZERO_ADDRESS]]
]));
const depositPwrd = async () => multiCall(encodeCalls([
[tokenA, "approve", [depositHandler.address, AMOUNT]],
[depositHandler, "depositPwrd", [[AMOUNT, 0, 0], 1, ZERO_ADDRESS]]
]));

const withdrawByLPToken = async () => multiCall(encodeCalls([
[withdrawHandler, "withdrawByLPToken", [true, AMOUNT, [1, 1, 1]]]
]));
const withdrawByStablecoin = async () => multiCall(encodeCalls([
[withdrawHandler, "withdrawByStablecoin", [true, 0, AMOUNT, 1]]
]));
const withdrawAllSingle = async () => multiCall(encodeCalls([
[withdrawHandler, "withdrawAllSingle", [true, 0, 1]]
]));
const withdrawAllBalanced = async () => multiCall(encodeCalls([
[withdrawHandler, "withdrawAllBalanced", [true, [1, 1, 1]]]
]));

it("should allow deposits (1/2)", async () => {
const { success, error } = await depositGvt();
assert.isTrue(success, `deposit1 failed: "${error}"`);
});
it("should allow deposits (2/2)", async () => {
const { success, error } = await depositPwrd();
assert.isTrue(success, `deposit2 failed: "${error}"`);
});

it("should allow withdrawals (1/4)", async () => {
await depositGvt();
const { success, error } = await withdrawByLPToken();
assert.isTrue(success, `withdraw1 failed: "${error}"`);
});
it("should allow withdrawals (2/4)", async () => {
await depositGvt();
const { success, error } = await withdrawByStablecoin();
assert.isTrue(success, `withdraw2 failed: "${error}"`);
});
it("should allow withdrawals (3/4)", async () => {
await depositGvt();
const { success, error } = await withdrawAllSingle();
assert.isTrue(success, `withdraw3 failed: "${error}"`);
});
it("should allow withdrawals (4/4)", async () => {
await depositGvt();
const { success, error } = await withdrawAllBalanced();
assert.isTrue(success, `withdraw4 failed: "${error}"`);
});

it("should not allow direct transfers to deposit handler", async () => {
const { success, error } = await multiCall(encodeCalls([[weth, "transfer", [depositHandler.address, AMOUNT]]]));
assert.isFalse(success, "transfer should have failed");
assert.equal(error, "TM: call not authorised");
});

it("should not allow direct transfers to withdraw handler", async () => {
const { success, error } = await multiCall(encodeCalls([[weth, "transfer", [withdrawHandler.address, AMOUNT]]]));
assert.isFalse(success, "transfer should have failed");
assert.equal(error, "TM: call not authorised");
});

it("should not allow unsupported method (deposit handler)", async () => {
const { success, error } = await multiCall(encodeCalls([[depositHandler, "referral", [ZERO_ADDRESS]]]));
assert.isFalse(success, "referral() should have failed");
assert.equal(error, "TM: call not authorised");
});

it("should not allow unsupported method (withdraw handler)", async () => {
const { success, error } = await multiCall(encodeCalls([[withdrawHandler, "withdrawalFee", [true]]]));
assert.isFalse(success, "withdrawalFee() should have failed");
assert.equal(error, "TM: call not authorised");
});

it("should not allow sending ETH to deposit handler", async () => {
const { success, error } = await multiCall([encodeTransaction(depositHandler.address, AMOUNT, "0x")]);
assert.isFalse(success, "sending ETH to deposit handler should have failed");
assert.equal(error, "TM: call not authorised");
});

it("should not allow sending ETH to withdraw handler", async () => {
const { success, error } = await multiCall([encodeTransaction(withdrawHandler.address, AMOUNT, "0x")]);
assert.isFalse(success, "sending ETH to withdrawal handler should have failed");
assert.equal(error, "TM: call not authorised");
});
});

0 comments on commit a61465f

Please sign in to comment.