-
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
3 changed files
with
167 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,58 @@ | ||
# pragma version 0.3.10 | ||
# pragma optimize gas | ||
# pragma evm-version cancun | ||
|
||
from vyper.interfaces import ERC20 | ||
|
||
interface Pool: | ||
def exchange(i: int128, j: int128, _dx: uint256, _min_dy: uint256) -> uint256: nonpayable | ||
|
||
weth: public(immutable(ERC20)) | ||
yeth: public(immutable(ERC20)) | ||
management: public(address) | ||
pending_management: public(address) | ||
operator: public(address) | ||
|
||
@external | ||
def __init__(_weth: address, _yeth: address): | ||
weth = ERC20(_weth) | ||
yeth = ERC20(_yeth) | ||
self.management = msg.sender | ||
self.operator = msg.sender | ||
|
||
@external | ||
def convert(_pool: address, _i: int128, _j: int128, _amount: uint256, _min_amount_out: uint256): | ||
assert msg.sender == self.operator | ||
assert _pool != empty(address) | ||
assert _amount > 0 | ||
assert _min_amount_out >= _amount | ||
assert weth.transferFrom(self.management, self, _amount, default_return_value=True) | ||
assert weth.approve(_pool, _amount, default_return_value=True) | ||
Pool(_pool).exchange(_i, _j, _amount, _min_amount_out) | ||
amount_out: uint256 = yeth.balanceOf(self) | ||
assert amount_out >= _min_amount_out | ||
assert yeth.transfer(self.management, amount_out, default_return_value=True) | ||
|
||
@external | ||
def rescue(_token: address, _amount: uint256 = max_value(uint256)): | ||
assert msg.sender == self.management | ||
amount: uint256 = _amount | ||
if _amount == max_value(uint256): | ||
amount = ERC20(_token).balanceOf(self) | ||
assert ERC20(_token).transfer(msg.sender, amount, default_return_value=True) | ||
|
||
@external | ||
def set_operator(_operator: address): | ||
assert msg.sender == self.management | ||
self.operator = _operator | ||
|
||
@external | ||
def set_management(_management: address): | ||
assert msg.sender == self.management | ||
self.pending_management = _management | ||
|
||
@external | ||
def accept_management(): | ||
assert msg.sender == self.pending_management | ||
self.pending_management = empty(address) | ||
self.management = msg.sender |
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,26 @@ | ||
# pragma version 0.3.10 | ||
# pragma optimize gas | ||
# pragma evm-version cancun | ||
|
||
from vyper.interfaces import ERC20 | ||
|
||
interface Pool: | ||
def exchange(i: int128, j: int128, _dx: uint256, _min_dy: uint256) -> uint256: nonpayable | ||
implements: Pool | ||
|
||
weth: immutable(ERC20) | ||
amount_out: uint256 | ||
|
||
@external | ||
def __init__(_weth: address): | ||
weth = ERC20(_weth) | ||
|
||
@external | ||
def set_amount_out(_amount_out: uint256): | ||
self.amount_out = _amount_out | ||
|
||
@external | ||
def exchange(i: int128, j: int128, _dx: uint256, _min_dy: uint256) -> uint256: | ||
# malicious pool only takes WETH and gives nothing in return | ||
weth.transferFrom(msg.sender, self, _dx, default_return_value=True) | ||
return self.amount_out |
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,83 @@ | ||
import ape | ||
from ape import Contract | ||
import pytest | ||
|
||
POOL = '0x69ACcb968B19a53790f43e57558F5E443A91aF22' | ||
ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' | ||
UNIT = 10**18 | ||
|
||
@pytest.fixture | ||
def operator(accounts): | ||
return accounts[4] | ||
|
||
@pytest.fixture | ||
def weth(): | ||
return Contract('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2') | ||
|
||
@pytest.fixture | ||
def yeth(): | ||
return Contract('0x1BED97CBC3c24A4fb5C069C6E311a967386131f7') | ||
|
||
@pytest.fixture | ||
def converter(project, deployer, operator, weth, yeth): | ||
converter = project.Converter.deploy(weth, yeth, sender=deployer) | ||
converter.set_operator(operator, sender=deployer) | ||
return converter | ||
|
||
def test_convert(deployer, operator, alice, weth, yeth, converter): | ||
weth.deposit(value=3*UNIT, sender=deployer) | ||
weth.approve(converter, UNIT, sender=deployer) | ||
|
||
min_out = UNIT * 1001 // 1000 | ||
|
||
# permissioned | ||
with ape.reverts(): | ||
converter.convert(POOL, 0, 1, UNIT, min_out, sender=alice) | ||
|
||
# must be at least 1:1 | ||
with ape.reverts(): | ||
converter.convert(POOL, 0, 1, UNIT, UNIT - 1, sender=operator) | ||
|
||
assert weth.balanceOf(deployer) == 3 * UNIT | ||
assert yeth.balanceOf(deployer) == 0 | ||
assert weth.balanceOf(converter) == 0 | ||
assert yeth.balanceOf(converter) == 0 | ||
converter.convert(POOL, 0, 1, UNIT, min_out, sender=operator) | ||
assert weth.balanceOf(deployer) == 2 * UNIT | ||
assert yeth.balanceOf(deployer) > UNIT | ||
assert weth.balanceOf(converter) == 0 | ||
assert yeth.balanceOf(converter) == 0 | ||
|
||
def test_malicious_pool(project, deployer, operator, weth, converter): | ||
weth.deposit(value=3*UNIT, sender=deployer) | ||
weth.approve(converter, UNIT, sender=deployer) | ||
malicious = project.MockMaliciousPool.deploy(weth, sender=deployer) | ||
with ape.reverts(): | ||
converter.convert(malicious, 0, 1, UNIT, UNIT, sender=operator) | ||
malicious.set_amount_out(UNIT, sender=deployer) | ||
with ape.reverts(): | ||
converter.convert(malicious, 0, 1, UNIT, UNIT, sender=operator) | ||
converter.convert(POOL, 0, 1, UNIT, UNIT, sender=operator) | ||
|
||
def test_set_operator(deployer, operator, alice, converter): | ||
assert converter.operator() == operator | ||
with ape.reverts(): | ||
converter.set_operator(alice, sender=alice) | ||
converter.set_operator(alice, sender=deployer) | ||
assert converter.operator() == alice | ||
|
||
def test_set_management(deployer, alice, bob, converter): | ||
assert converter.management() == deployer | ||
assert converter.pending_management() == ZERO_ADDRESS | ||
with ape.reverts(): | ||
converter.set_management(alice, sender=alice) | ||
with ape.reverts(): | ||
converter.accept_management(sender=alice) | ||
converter.set_management(alice, sender=deployer) | ||
assert converter.management() == deployer | ||
assert converter.pending_management() == alice | ||
with ape.reverts(): | ||
converter.accept_management(sender=bob) | ||
converter.accept_management(sender=alice) | ||
assert converter.management() == alice | ||
assert converter.pending_management() == ZERO_ADDRESS |