Skip to content

Commit

Permalink
Merge pull request #3 from curvefi/feat-migrator
Browse files Browse the repository at this point in the history
Pool Migrator
  • Loading branch information
iamdefinitelyahuman authored Mar 1, 2021
2 parents 1f0f50d + 380dfae commit d6f0ef7
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 0 deletions.
44 changes: 44 additions & 0 deletions contracts/PoolMigrator.vy
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# @version 0.2.11
"""
@title Pool Migrator
@author Curve.fi
@notice Zap for moving liquidity between Curve factory pools in a single transaction
@license MIT
"""

interface ERC20:
def approve(_spender: address, _amount: uint256): nonpayable

interface Swap:
def transferFrom(_owner: address, _spender: address, _amount: uint256) -> bool: nonpayable
def add_liquidity(_amounts: uint256[2], _min_mint_amount: uint256, _receiver: address) -> uint256: nonpayable
def remove_liquidity(_burn_amount: uint256, _min_amounts: uint256[2]) -> uint256[2]: nonpayable
def coins(i: uint256) -> address: view


# pool -> coins are approved?
is_approved: HashMap[address, bool]


@external
def migrate_to_new_pool(_old_pool: address, _new_pool: address, _amount: uint256) -> uint256:
"""
@notice Migrate liquidity between two pools
@dev Each pool must be deployed by the curve factory and contain identical
assets. The migrator must have approval to transfer `_old_pool` tokens
on behalf of the caller.
@param _old_pool Address of the pool to migrate from
@param _new_pool Address of the pool to migrate into
@param _amount Number of `_old_pool` LP tokens to migrate
@return uint256 Number of `_new_pool` LP tokens received
"""
Swap(_old_pool).transferFrom(msg.sender, self, _amount)
amounts: uint256[2] = Swap(_old_pool).remove_liquidity(_amount, [0, 0])

if not self.is_approved[_new_pool]:
for i in range(2):
coin: address = Swap(_new_pool).coins(i)
ERC20(coin).approve(_new_pool, MAX_UINT256)
self.is_approved[_new_pool] = True

return Swap(_new_pool).add_liquidity(amounts, 0, msg.sender)
78 changes: 78 additions & 0 deletions tests/test_migrator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import brownie
import pytest

pytestmark = pytest.mark.usefixtures("add_initial_liquidity", "mint_bob", "approve_bob")


@pytest.fixture(scope="module")
def swap2(MetaImplementationUSD, alice, base_pool, factory, coin):
tx = factory.deploy_metapool(
base_pool, "Test Swap", "TST", coin, 200, 4000000, {"from": alice}
)
yield MetaImplementationUSD.at(tx.return_value)


@pytest.fixture(scope="module")
def migrator(PoolMigrator, alice, swap):
contract = PoolMigrator.deploy({"from": alice})
swap.approve(contract, 2 ** 256 - 1, {"from": alice})
yield contract


def test_migrate(alice, migrator, swap, swap2):
balance = swap.balanceOf(alice)
migrator.migrate_to_new_pool(swap, swap2, balance, {"from": alice})

assert swap.balanceOf(alice) == 0
assert swap2.balanceOf(alice) == balance

assert swap.balanceOf(migrator) == 0
assert swap2.balanceOf(migrator) == 0


def test_migrate_partial(alice, migrator, swap, swap2, coin):
balance = swap.balanceOf(alice)
migrator.migrate_to_new_pool(swap, swap2, balance // 4, {"from": alice})

assert swap.balanceOf(alice) == balance - balance // 4
assert abs(swap2.balanceOf(alice) - balance // 4) < 8 + (10 ** 18 - coin.decimals())

assert swap.balanceOf(migrator) == 0
assert swap2.balanceOf(migrator) == 0


def test_migrate_multiple(alice, migrator, swap, swap2, coin):
balance = swap.balanceOf(alice)
for i in range(4):
migrator.migrate_to_new_pool(swap, swap2, balance // 4, {"from": alice})

assert swap.balanceOf(alice) < 5
assert abs(swap2.balanceOf(alice) - balance) < 8 + (10 ** 18 - coin.decimals())

assert swap.balanceOf(migrator) == 0
assert swap2.balanceOf(migrator) == 0


def test_migrate_bidirectional(alice, migrator, swap, swap2):
balance = swap.balanceOf(alice)
migrator.migrate_to_new_pool(swap, swap2, balance, {"from": alice})
swap2.approve(migrator, 2 ** 256 - 1, {"from": alice})
migrator.migrate_to_new_pool(swap2, swap, balance, {"from": alice})

assert abs(swap.balanceOf(alice) - balance) < 4
assert swap2.balanceOf(alice) == 0

assert swap.balanceOf(migrator) == 0
assert swap2.balanceOf(migrator) == 0


def test_migration_wrong_pool(alice, migrator, swap, swap_btc):
balance = swap.balanceOf(alice)
with brownie.reverts():
migrator.migrate_to_new_pool(swap, swap_btc, balance, {"from": alice})


def test_insufficient_balance(alice, migrator, swap, swap2):
balance = swap.balanceOf(alice)
with brownie.reverts():
migrator.migrate_to_new_pool(swap, swap2, balance + 1, {"from": alice})

0 comments on commit d6f0ef7

Please sign in to comment.