Skip to content

Commit

Permalink
Merge branch 'dev' into feat/roll-return-values
Browse files Browse the repository at this point in the history
  • Loading branch information
alcueca authored Apr 26, 2021
2 parents 1afc0f5 + 78e173c commit d05428e
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 34 deletions.
45 changes: 17 additions & 28 deletions contracts/Ladle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,12 @@ contract Ladle is AccessControl() {
}

ICauldron public immutable cauldron;
address public poolRouter;

mapping (bytes6 => IJoin) public joins; // Join contracts available to manage assets. The same Join can serve multiple assets (ETH-A, ETH-B, etc...)
mapping (bytes6 => IPool) public pools; // Pool contracts available to manage series. 12 bytes still free.

event JoinAdded(bytes6 indexed assetId, address indexed join);
event PoolAdded(bytes6 indexed seriesId, address indexed pool);
event PoolRouterSet(address indexed poolRouter);

constructor (ICauldron cauldron_) {
cauldron = cauldron_;
Expand Down Expand Up @@ -123,15 +121,6 @@ contract Ladle is AccessControl() {
emit PoolAdded(seriesId, address(pool));
}

/// @dev Set the Pool Router for this Ladle
function setPoolRouter(address poolRouter_)
external
auth
{
poolRouter = poolRouter_;
emit PoolRouterSet(poolRouter_);
}

// ---- Batching ----


Expand All @@ -142,8 +131,8 @@ contract Ladle is AccessControl() {
Operation[] calldata operations,
bytes[] calldata data
) external payable {
require(operations.length == data.length, "Unmatched operation data");
bytes12 vaultId_;
require(operations.length == data.length, "Mismatched operation data");
bytes12 cachedId;
DataTypes.Vault memory vault;

// Execute all operations in the batch. Conditionals ordered by expected frequency.
Expand All @@ -153,7 +142,7 @@ contract Ladle is AccessControl() {

if (operation == Operation.BUILD) {
(bytes12 vaultId, bytes6 seriesId, bytes6 ilkId) = abi.decode(data[i], (bytes12, bytes6, bytes6));
vault = _build(vaultId, seriesId, ilkId); // Cache the vault that was just built
(cachedId, vault) = (vaultId, _build(vaultId, seriesId, ilkId)); // Cache the vault that was just built

} else if (operation == Operation.FORWARD_PERMIT) {
(bytes6 id, bool asset, address spender, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) =
Expand All @@ -166,18 +155,18 @@ contract Ladle is AccessControl() {

} else if (operation == Operation.POUR) {
(bytes12 vaultId, address to, int128 ink, int128 art) = abi.decode(data[i], (bytes12, address, int128, int128));
if (vaultId_ != vaultId) vault = getOwnedVault(vaultId);
if (cachedId != vaultId) (cachedId, vault) = (vaultId, getOwnedVault(vaultId));
_pour(vaultId, vault, to, ink, art);

} else if (operation == Operation.SERVE) {
(bytes12 vaultId, address to, uint128 ink, uint128 base, uint128 max) = abi.decode(data[i], (bytes12, address, uint128, uint128, uint128));
if (vaultId_ != vaultId) vault = getOwnedVault(vaultId);
if (cachedId != vaultId) (cachedId, vault) = (vaultId, getOwnedVault(vaultId));
_serve(vaultId, vault, to, ink, base, max);

} else if (operation == Operation.ROLL) {
(bytes12 vaultId, bytes6 newSeriesId, uint128 max) = abi.decode(data[i], (bytes12, bytes6, uint128));
if (vaultId_ != vaultId) vault = getOwnedVault(vaultId);
(vault,) = _roll(vaultId, vault, newSeriesId, max); // TODO: _roll must return vault and balances
if (cachedId != vaultId) (cachedId, vault) = (vaultId, getOwnedVault(vaultId));
(vault,) = _roll(vaultId, vault, newSeriesId, max);

} else if (operation == Operation.FORWARD_DAI_PERMIT) {
(bytes6 id, bool asset, address spender, uint256 nonce, uint256 deadline, bool allowed, uint8 v, bytes32 r, bytes32 s) =
Expand All @@ -202,17 +191,17 @@ contract Ladle is AccessControl() {

} else if (operation == Operation.CLOSE) {
(bytes12 vaultId, address to, int128 ink, int128 art) = abi.decode(data[i], (bytes12, address, int128, int128));
if (vaultId_ != vaultId) vault = getOwnedVault(vaultId);
if (cachedId != vaultId) (cachedId, vault) = (vaultId, getOwnedVault(vaultId));
_close(vaultId, vault, to, ink, art);

} else if (operation == Operation.REPAY) {
(bytes12 vaultId, address to, int128 ink, uint128 min) = abi.decode(data[i], (bytes12, address, int128, uint128));
if (vaultId_ != vaultId) vault = getOwnedVault(vaultId);
if (cachedId != vaultId) (cachedId, vault) = (vaultId, getOwnedVault(vaultId));
_repay(vaultId, vault, to, ink, min);

} else if (operation == Operation.REPAY_VAULT) {
(bytes12 vaultId, address to, int128 ink, uint128 max) = abi.decode(data[i], (bytes12, address, int128, uint128));
if (vaultId_ != vaultId) vault = getOwnedVault(vaultId);
if (cachedId != vaultId) (cachedId, vault) = (vaultId, getOwnedVault(vaultId));
_repayVault(vaultId, vault, to, ink, max);

} else if (operation == Operation.TRANSFER_TO_FYTOKEN) {
Expand All @@ -227,33 +216,33 @@ contract Ladle is AccessControl() {

} else if (operation == Operation.STIR_FROM) {
(bytes12 vaultId, bytes12 to, uint128 ink, uint128 art) = abi.decode(data[i], (bytes12, bytes12, uint128, uint128));
if (vaultId_ != vaultId) vault = getOwnedVault(vaultId);
if (cachedId != vaultId) (cachedId, vault) = (vaultId, getOwnedVault(vaultId));
_stirFrom(vaultId, to, ink, art);

} else if (operation == Operation.STIR_TO) {
(bytes12 from, bytes12 vaultId, uint128 ink, uint128 art) = abi.decode(data[i], (bytes12, bytes12, uint128, uint128));
if (vaultId_ != vaultId) vault = getOwnedVault(vaultId);
if (cachedId != vaultId) (cachedId, vault) = (vaultId, getOwnedVault(vaultId));
_stirTo(from, vaultId, ink, art);

} else if (operation == Operation.TWEAK) {
(bytes12 vaultId, bytes6 seriesId, bytes6 ilkId) = abi.decode(data[i], (bytes12, bytes6, bytes6));
if (vaultId_ != vaultId) vault = getOwnedVault(vaultId);
if (cachedId != vaultId) (cachedId, vault) = (vaultId, getOwnedVault(vaultId));
vault = _tweak(vaultId, seriesId, ilkId);

} else if (operation == Operation.GIVE) {
(bytes12 vaultId, address to) = abi.decode(data[i], (bytes12, address));
if (vaultId_ != vaultId) vault = getOwnedVault(vaultId);
if (cachedId != vaultId) (cachedId, vault) = (vaultId, getOwnedVault(vaultId));
vault = _give(vaultId, to);
delete vault; // Clear the cache, since the vault doesn't necessarily belong to msg.sender anymore
cachedId = bytes12(0);

} else if (operation == Operation.DESTROY) {
(bytes12 vaultId) = abi.decode(data[i], (bytes12));
if (vaultId_ != vaultId) vault = getOwnedVault(vaultId);
if (cachedId != vaultId) (cachedId, vault) = (vaultId, getOwnedVault(vaultId));
_destroy(vaultId);
delete vault; // Clear the cache
cachedId = bytes12(0);

} else {
revert("Invalid operation");
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion test/064_ladle_stir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ describe('Ladle - stir', function () {
vaultFromId = (env.vaults.get(seriesId) as Map<string, string>).get(ilkId) as string

// ==== Set testing environment ====
await cauldron.build(owner, vaultToId, seriesId, ilkId)
await ladle.build(vaultToId, seriesId, ilkId)
})

it('does not allow moving collateral other than to the origin vault owner', async () => {
Expand Down
119 changes: 114 additions & 5 deletions test/070_ladle_batch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ describe('Ladle - batch', function () {
let weth: WETH9Mock

async function fixture() {
return await YieldEnvironment.setup(ownerAcc, [baseId, ilkId], [seriesId])
return await YieldEnvironment.setup(ownerAcc, [baseId, ilkId, otherIlkId], [seriesId])
}

before(async () => {
Expand All @@ -50,9 +50,11 @@ describe('Ladle - batch', function () {

const baseId = ethers.utils.hexlify(ethers.utils.randomBytes(6))
const ilkId = ethers.utils.hexlify(ethers.utils.randomBytes(6))
const otherIlkId = ethers.utils.hexlify(ethers.utils.randomBytes(6))
const ethId = ethers.utils.formatBytes32String('ETH').slice(0, 14)
const seriesId = ethers.utils.hexlify(ethers.utils.randomBytes(6))
const vaultId = ethers.utils.hexlify(ethers.utils.randomBytes(12))
const otherVaultId = ethers.utils.hexlify(ethers.utils.randomBytes(12))
let ethVaultId: string

beforeEach(async () => {
Expand All @@ -72,7 +74,42 @@ describe('Ladle - batch', function () {
ethVaultId = (env.vaults.get(seriesId) as Map<string, string>).get(ethId) as string
})

it('builds a vault, permit and serve', async () => {
it('operations and their data must match in length', async () => {
await expect(ladle.ladle.batch([0], [])).to.be.revertedWith('Mismatched operation data')
})

it('builds a vault, tweaks it and gives it', async () => {
await ladle.batch([
ladle.buildAction(vaultId, seriesId, ilkId),
ladle.tweakAction(vaultId, seriesId, otherIlkId),
ladle.giveAction(vaultId, other),
])
})

it('builds two vaults and gives them', async () => {
await ladle.batch([
ladle.buildAction(vaultId, seriesId, ilkId),
ladle.giveAction(vaultId, other),
ladle.buildAction(otherVaultId, seriesId, ilkId),
ladle.giveAction(otherVaultId, other),
])
})

it('builds a vault and destroys it', async () => {
await ladle.batch([ladle.buildAction(vaultId, seriesId, ilkId), ladle.destroyAction(vaultId)])
})

it("after giving a vault, it can't tweak it", async () => {
await expect(
ladle.batch([
ladle.buildAction(vaultId, seriesId, ilkId),
ladle.giveAction(vaultId, other),
ladle.tweakAction(vaultId, seriesId, otherIlkId),
])
).to.be.revertedWith('Only vault owner')
})

it('builds a vault, permit and pour', async () => {
const ilkSeparator = await ilk.DOMAIN_SEPARATOR()
const deadline = MAX
const posted = WAD.mul(2)
Expand All @@ -90,7 +127,7 @@ describe('Ladle - batch', function () {
await ladle.batch([
ladle.buildAction(vaultId, seriesId, ilkId),
ladle.forwardPermitAction(ilkId, true, ilkJoin.address, posted, deadline, v, r, s),
ladle.serveAction(vaultId, owner, posted, borrowed, MAX),
ladle.pourAction(vaultId, owner, posted, borrowed),
])

const vault = await cauldron.vaults(vaultId)
Expand Down Expand Up @@ -119,7 +156,7 @@ describe('Ladle - batch', function () {
expect(vault.ilkId).to.equal(ethId)
})

it('users can transfer ETH then pour, then serve in a single transaction with multicall', async () => {
it('users can transfer ETH then pour, then serve', async () => {
const posted = WAD.mul(2)
const borrowed = WAD

Expand All @@ -133,13 +170,85 @@ describe('Ladle - batch', function () {
)
})

it('users can transfer ETH then pour, then close', async () => {
const posted = WAD.mul(2)
const borrowed = WAD

await ladle.batch(
[
ladle.joinEtherAction(ethId),
ladle.pourAction(ethVaultId, owner, posted, borrowed),
ladle.closeAction(ethVaultId, other, 0, borrowed.div(2).mul(-1)),
],
{ value: posted }
)
})

it('users can transfer to a pool and repay in a batch', async () => {
const separator = await base.DOMAIN_SEPARATOR()
const deadline = MAX
const amount = WAD
const nonce = await base.nonces(owner)
const approval = {
owner: owner,
spender: ladle.address,
value: amount,
}
const permitDigest = signatures.getPermitDigest(separator, approval, nonce, deadline)

const { v, r, s } = signatures.sign(permitDigest, signatures.privateKey0)

const posted = WAD.mul(2)
const borrowed = WAD

await ladle.batch([
ladle.buildAction(vaultId, seriesId, ilkId),
ladle.pourAction(vaultId, owner, posted, borrowed),
ladle.forwardPermitAction(baseId, true, ladle.address, amount, deadline, v, r, s),
ladle.transferToPoolAction(seriesId, true, WAD.div(2)),
ladle.repayAction(vaultId, other, 0, 0),
])
})

it('users can transfer to a pool and repay a whole vault in a batch', async () => {
const separator = await base.DOMAIN_SEPARATOR()
const deadline = MAX
const amount = WAD
const nonce = await base.nonces(owner)
const approval = {
owner: owner,
spender: ladle.address,
value: amount,
}
const permitDigest = signatures.getPermitDigest(separator, approval, nonce, deadline)

const { v, r, s } = signatures.sign(permitDigest, signatures.privateKey0)

const posted = WAD.mul(2)
const borrowed = WAD

await ladle.batch([
ladle.buildAction(vaultId, seriesId, ilkId),
ladle.pourAction(vaultId, owner, posted, borrowed),
ladle.forwardPermitAction(baseId, true, ladle.address, amount, deadline, v, r, s),
ladle.transferToPoolAction(seriesId, true, WAD),
ladle.repayVaultAction(vaultId, other, 0, MAX),
])
})

it('calls can be routed to pools', async () => {
await ladle.build(vaultId, seriesId, ilkId)
await base.mint(pool.address, WAD)

const retrieveBaseTokenCall = pool.interface.encodeFunctionData('retrieveBaseToken', [owner])
await expect(await ladle.route(seriesId, retrieveBaseTokenCall))
.to.emit(base, 'Transfer')
.withArgs(pool.address, owner, WAD)
})

it('errors bubble up from calls routed to pools', async () => {
await base.mint(pool.address, WAD)

const sellBaseTokenCall = pool.interface.encodeFunctionData('sellBaseToken', [owner, MAX128])
await expect(ladle.route(seriesId, sellBaseTokenCall)).to.be.revertedWith('Pool: Not enough fyToken obtained')
})
})
6 changes: 6 additions & 0 deletions test/081_witch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ describe('Witch', function () {
await expect(witch.buy(vaultId, WAD, WAD)).to.be.revertedWith('Not enough bought')
})

it('it can buy no collateral (coverage)', async () => {
expect(await witch.buy(vaultId, 0, 0))
.to.emit(witch, 'Bought')
.withArgs(owner, vaultId, 0, 0)
})

it('allows to buy 1/2 of the collateral for the whole debt at the beginning', async () => {
const baseBalanceBefore = await base.balanceOf(owner)
const ilkBalanceBefore = await ilk.balanceOf(owner)
Expand Down

0 comments on commit d05428e

Please sign in to comment.