forked from agregkit/argent-contracts
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
359 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | ||
}); | ||
}); |