Skip to content

Commit

Permalink
Add multicall and converter in LockstakeEngine (#7)
Browse files Browse the repository at this point in the history
* Support multicall for LockstakeEngine

* Sketch use of converter inside LockstakeEngine

* Send out ngt from converter, separate events

* Add index parameter to open()

* Approve tokens to mkrNgt in the constructor

* gov => mkr, stkGov => stkMkr

* public => external

Co-authored-by: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com>

* Change revert message

* new line at end of file

Co-authored-by: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com>

* Put ngt functions after regular ones

* Change order of events as well

Co-authored-by: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com>

* ngt tests and testing open with wrong index

---------

Co-authored-by: sunbreak <sunbreak1211@proton.me>
Co-authored-by: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com>
  • Loading branch information
3 people authored Dec 20, 2023
1 parent 87b9a68 commit bc63780
Show file tree
Hide file tree
Showing 4 changed files with 399 additions and 210 deletions.
100 changes: 71 additions & 29 deletions src/LockstakeEngine.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
pragma solidity ^0.8.16;

import { LockstakeUrn } from "src/LockstakeUrn.sol";
import { Multicall } from "src/Multicall.sol";

interface DelegateFactoryLike {
function gov() external view returns (GemLike);
Expand Down Expand Up @@ -55,7 +56,14 @@ interface JugLike {
function drip(bytes32) external returns (uint256);
}

contract LockstakeEngine {
interface MkrNgtLike {
function rate() external view returns (uint256);
function ngt() external view returns (address);
function ngtToMkr(address, uint256) external;
function mkrToNgt(address, uint256) external;
}

contract LockstakeEngine is Multicall {
// --- storage variables ---

mapping(address => uint256) public wards; // usr => 1 == access
Expand All @@ -79,9 +87,12 @@ contract LockstakeEngine {
NstJoinLike immutable public nstJoin;
GemLike immutable public nst;
bytes32 immutable public ilk;
GemLike immutable public gov;
GemLike immutable public stkGov;
GemLike immutable public mkr;
GemLike immutable public stkMkr;
uint256 immutable public fee;
MkrNgtLike immutable public mkrNgt;
GemLike immutable public ngt;
uint256 immutable public mkrNgtRate;

// --- events ---

Expand All @@ -95,7 +106,9 @@ contract LockstakeEngine {
event Nope(address indexed urn, address indexed usr);
event Delegate(address indexed urn, address indexed delegate);
event Lock(address indexed urn, uint256 wad);
event LockNgt(address indexed urn, uint256 ngtWad);
event Free(address indexed urn, address indexed to, uint256 wad, uint256 burn);
event FreeNgt(address indexed urn, address indexed to, uint256 ngtWad, uint256 burn);
event Draw(address indexed urn, uint256 wad);
event Wipe(address indexed urn, uint256 wad);
event SelectFarm(address indexed urn, address farm);
Expand All @@ -121,17 +134,23 @@ contract LockstakeEngine {

// --- constructor ---

constructor(address delegateFactory_, address nstJoin_, bytes32 ilk_, address stkGov_, uint256 fee_) {
constructor(address delegateFactory_, address nstJoin_, bytes32 ilk_, address stkMkr_, uint256 fee_, address mkrNgt_) {
delegateFactory = DelegateFactoryLike(delegateFactory_);
nstJoin = NstJoinLike(nstJoin_);
vat = nstJoin.vat();
nst = nstJoin.nst();
ilk = ilk_;
gov = delegateFactory.gov();
stkGov = GemLike(stkGov_);
mkr = delegateFactory.gov();
stkMkr = GemLike(stkMkr_);
fee = fee_;
nst.approve(nstJoin_, type(uint256).max);
vat.hope(nstJoin_);
mkrNgt = MkrNgtLike(mkrNgt_);
ngt = GemLike(mkrNgt.ngt());
ngt.approve(address(mkrNgt), type(uint256).max);
mkr.approve(address(mkrNgt), type(uint256).max);
mkrNgtRate = mkrNgt.rate();

wards[msg.sender] = 1;
emit Rely(msg.sender);
}
Expand Down Expand Up @@ -181,7 +200,7 @@ contract LockstakeEngine {

function getUrn(address owner, uint256 index) external view returns (address urn) {
uint256 salt = uint256(keccak256(abi.encode(owner, index)));
bytes32 codeHash = keccak256(abi.encodePacked(type(LockstakeUrn).creationCode, abi.encode(vat, stkGov)));
bytes32 codeHash = keccak256(abi.encodePacked(type(LockstakeUrn).creationCode, abi.encode(vat, stkMkr)));
urn = address(uint160(uint256(
keccak256(
abi.encodePacked(bytes1(0xff), address(this), salt, codeHash)
Expand All @@ -192,12 +211,12 @@ contract LockstakeEngine {
function isUrnAuth(address urn, address usr) external view returns (bool ok) {
ok = _urnAuth(urn, usr);
}

// --- urn/delegation functions ---

function open() external returns (address urn) {
bytes32 salt = keccak256(abi.encode(msg.sender, usrAmts[msg.sender]++));
urn = address(new LockstakeUrn{salt: salt}(address(vat), address(stkGov)));
function open(uint256 index) external returns (address urn) {
require(index == usrAmts[msg.sender]++, "LockstakeEngine/wrong-urn-index");
bytes32 salt = keccak256(abi.encode(msg.sender, index));
urn = address(new LockstakeUrn{salt: salt}(address(vat), address(stkMkr)));
urnOwners[urn] = msg.sender;
emit Open(msg.sender, urn);
}
Expand All @@ -213,34 +232,57 @@ contract LockstakeEngine {
}

function lock(address urn, uint256 wad) external urnAuth(urn) {
mkr.transferFrom(msg.sender, address(this), wad);
_lock(urn, wad);
emit Lock(urn, wad);
}

function lockNgt(address urn, uint256 ngtWad) external urnAuth(urn) {
ngt.transferFrom(msg.sender, address(this), ngtWad);
mkrNgt.ngtToMkr(address(this), ngtWad);
_lock(urn, ngtWad / mkrNgtRate);
emit LockNgt(urn, ngtWad);
}

function _lock(address urn, uint256 wad) internal {
require(wad <= uint256(type(int256).max), "LockstakeEngine/wad-overflow");
gov.transferFrom(msg.sender, address(this), wad);
address delegate_ = urnDelegates[urn];
if (delegate_ != address(0)) {
gov.approve(address(delegate_), wad);
mkr.approve(address(delegate_), wad);
DelegateLike(delegate_).lock(wad);
}
// TODO: define if we want an internal registry to register how much is locked per user,
// the vat.slip and stkGov balance act already as a registry so probably not needed an extra one
// the vat.slip and stkMkr balance act already as a registry so probably not needed an extra one
vat.slip(ilk, urn, int256(wad));
vat.frob(ilk, urn, urn, address(0), int256(wad), 0);
stkGov.mint(urn, wad);
emit Lock(urn, wad);
stkMkr.mint(urn, wad);
}

function free(address urn, address to, uint256 wad) external urnAuth(urn) {
uint256 freed = _free(urn, wad);
mkr.transfer(to, freed);
emit Free(urn, to, wad, wad - freed);
}

function freeNgt(address urn, address to, uint256 ngtWad) external urnAuth(urn) {
uint256 wad = ngtWad / mkrNgtRate;
uint256 freed = _free(urn, wad);
mkrNgt.mkrToNgt(to, freed);
emit FreeNgt(urn, to, ngtWad, wad - freed);
}

function _free(address urn, uint256 wad) internal returns (uint256 freed) {
require(wad <= uint256(type(int256).max), "LockstakeEngine/wad-overflow");
stkGov.burn(urn, wad);
stkMkr.burn(urn, wad);
vat.frob(ilk, urn, urn, address(0), -int256(wad), 0);
vat.slip(ilk, urn, -int256(wad));
address delegate_ = urnDelegates[urn];
if (delegate_ != address(0)) {
DelegateLike(delegate_).free(wad);
}
uint256 burn = wad * fee / WAD;
gov.transfer(to, wad - burn);
gov.burn(address(this), burn);
emit Free(urn, to, wad, burn);
mkr.burn(address(this), burn);
freed = wad - burn;
}

function delegate(address urn, address delegate_) external urnAuth(urn) {
Expand All @@ -253,7 +295,7 @@ contract LockstakeEngine {
DelegateLike(prevDelegate).free(wad);
}
if (delegate_ != address(0)) {
gov.approve(address(delegate_), wad);
mkr.approve(address(delegate_), wad);
DelegateLike(delegate_).lock(wad);
}
}
Expand Down Expand Up @@ -317,22 +359,22 @@ contract LockstakeEngine {
function onKick(address urn, uint256 wad) external auth {
address urnFarm = urnFarms[urn];
if (urnFarm != address(0)){
uint256 freed = GemLike(stkGov).balanceOf(address(urn));
uint256 freed = GemLike(stkMkr).balanceOf(address(urn));
if (wad > freed) {
LockstakeUrn(urn).withdraw(urnFarm, wad - freed);
}
}
stkGov.burn(urn, wad); // Burn the whole liquidated amount of staking token
stkMkr.burn(urn, wad); // Burn the whole liquidated amount of staking token
address delegate_ = urnDelegates[urn];
if (delegate_ != address(0)) {
DelegateLike(delegate_).free(wad); // Undelegate liquidated amount and retain the GOV tokens
DelegateLike(delegate_).free(wad); // Undelegate liquidated amount and retain the MKR tokens
}
// Urn confiscation happens in Dog contract where ilk vat.gem is sent to the LockstakeClipper
emit OnKick(urn, wad);
}

function onTake(address urn, address who, uint256 wad) external auth {
gov.transfer(who, wad); // Free GOV to the auction buyer
mkr.transfer(who, wad); // Free MKR to the auction buyer
emit OnTake(urn, who, wad);
}

Expand All @@ -344,22 +386,22 @@ contract LockstakeEngine {
} else {
unchecked { left = left - burn; }
}
gov.burn(address(this), burn); // Burn GOV
mkr.burn(address(this), burn); // Burn MKR
if (left > 0) {
address delegate_ = urnDelegates[urn];
if (delegate_ != address(0)) {
gov.approve(address(delegate_), left);
mkr.approve(address(delegate_), left);
DelegateLike(delegate_).lock(left);
}
vat.slip(ilk, urn, int256(left));
vat.frob(ilk, urn, urn, address(0), int256(left), 0);
stkGov.mint(urn, left);
stkMkr.mint(urn, left);
}
emit OnTakeLeftovers(urn, tot, left, burn);
}

function onYank(address urn, uint256 wad) external auth {
gov.burn(address(this), wad);
mkr.burn(address(this), wad);
emit OnYank(urn, wad);
}
}
26 changes: 26 additions & 0 deletions src/Multicall.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: GPL-2.0-or-later

// Based on https://github.com/Uniswap/v3-periphery/blob/697c2474757ea89fec12a4e6db16a574fe259610/contracts/base/Multicall.sol

pragma solidity ^0.8.16;

// Enables calling multiple methods in a single call to the contract
abstract contract Multicall {
function multicall(bytes[] calldata data) external returns (bytes[] memory results) {
results = new bytes[](data.length);
for (uint256 i = 0; i < data.length; i++) {
(bool success, bytes memory result) = address(this).delegatecall(data[i]);

if (!success) {
// Next 5 lines from https://ethereum.stackexchange.com/a/83577
if (result.length < 68) revert();
assembly {
result := add(result, 0x04)
}
revert(abi.decode(result, (string)));
}

results[i] = result;
}
}
}
Loading

0 comments on commit bc63780

Please sign in to comment.