Skip to content

Commit

Permalink
Merge branch 'dev' into delegate-urn-management
Browse files Browse the repository at this point in the history
  • Loading branch information
sunbreak1211 committed Dec 12, 2023
2 parents 08871ee + c7fa375 commit 6d76be6
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 53 deletions.
63 changes: 30 additions & 33 deletions src/LockstakeEngine.sol
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ contract LockstakeEngine {
mapping(address => address) public urnOwners; // urn => owner
mapping(address => mapping(address => uint256)) public urnCan; // urn => usr => allowed (1 = yes, 0 = no)
mapping(address => address) public urnDelegates; // urn => current associated delegate
mapping(address => address) public selectedFarm; // urn => current selected farm
mapping(address => address) public urnFarms; // urn => current selected farm
JugLike public jug;

// --- constants ---
Expand Down Expand Up @@ -95,13 +95,13 @@ 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 Free(address indexed urn, uint256 wad, uint256 burn);
event Free(address indexed urn, address indexed to, uint256 wad, uint256 burn);
event Draw(address indexed urn, uint256 wad);
event Wipe(address indexed urn, uint256 wad);
event SelectFarm(address indexed urn, address farm);
event Stake(address indexed urn, address indexed farm, uint256 wad, uint16 ref);
event Withdraw(address indexed urn, address indexed farm, uint256 amt);
event GetReward(address indexed urn, address indexed farm);
event Withdraw(address indexed urn, address indexed farm, uint256 wad);
event GetReward(address indexed urn, address indexed farm, address indexed to, uint256 amt);
event OnKick(address indexed urn, uint256 wad);
event OnTake(address indexed urn, address indexed who, uint256 wad);
event OnTakeLeftovers(address indexed urn, uint256 tot, uint256 left, uint256 burn);
Expand Down Expand Up @@ -197,12 +197,8 @@ contract LockstakeEngine {
// --- urn/delegation functions ---

function open() external returns (address urn) {
uint256 salt = uint256(keccak256(abi.encode(msg.sender, usrAmts[msg.sender]++)));
bytes memory code = abi.encodePacked(type(LockstakeUrn).creationCode, abi.encode(vat, stkGov));
assembly {
urn := create2(0, add(code, 0x20), mload(code), salt)
}
require(urn != address(0), "LockstakeEngine/urn-creation-failed");
bytes32 salt = keccak256(abi.encode(msg.sender, usrAmts[msg.sender]++));
urn = address(new LockstakeUrn{salt: salt}(address(vat), address(stkGov)));
urnOwners[urn] = msg.sender;
emit Open(msg.sender, urn);
}
Expand Down Expand Up @@ -233,19 +229,19 @@ contract LockstakeEngine {
emit Lock(urn, wad);
}

function free(address urn, uint256 wad) external urnAuth(urn) {
function free(address urn, address to, uint256 wad) external urnAuth(urn) {
require(wad <= uint256(type(int256).max), "LockstakeEngine/wad-overflow");
stkGov.burn(urn, wad);
vat.frob(ilk, urn, urn, address(0), -int256(wad), 0);
vat.slip(ilk, urn, -int256(wad));
stkGov.burn(urn, 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);
gov.transfer(msg.sender, wad - burn);
emit Free(urn, wad, burn);
emit Free(urn, to, wad, burn);
}

function delegate(address urn, address delegate_) external urnAuth(urn) {
Expand Down Expand Up @@ -291,45 +287,46 @@ contract LockstakeEngine {

function selectFarm(address urn, address farm) external urnAuth(urn) {
require(farms[farm] == 1, "LockstakeEngine/non-existing-farm");
address selectedFarmUrn = selectedFarm[urn];
require(selectedFarmUrn == address(0) || GemLike(selectedFarmUrn).balanceOf(address(urn)) == 0, "LockstakeEngine/withdraw-first");
selectedFarm[urn] = farm;
address urnFarm = urnFarms[urn];
require(urnFarm == address(0) || GemLike(urnFarm).balanceOf(address(urn)) == 0, "LockstakeEngine/withdraw-first");
urnFarms[urn] = farm;
emit SelectFarm(urn, farm);
}

function stake(address urn, uint256 wad, uint16 ref) external urnAuth(urn) {
address selectedFarmUrn = selectedFarm[urn];
require(selectedFarmUrn != address(0), "LockstakeEngine/missing-selected-farm");
LockstakeUrn(urn).stake(selectedFarmUrn, wad, ref);
emit Stake(urn, selectedFarmUrn, wad, ref);
address urnFarm = urnFarms[urn];
require(urnFarm != address(0), "LockstakeEngine/missing-selected-farm");
require(farms[urnFarm] == 1, "LockstakeEngine/selected-farm-not-available-anymore");
LockstakeUrn(urn).stake(urnFarm, wad, ref);
emit Stake(urn, urnFarm, wad, ref);
}

function withdraw(address urn, uint256 amt) external urnAuth(urn) {
address selectedFarmUrn = selectedFarm[urn];
require(selectedFarmUrn != address(0), "LockstakeEngine/missing-selected-farm");
LockstakeUrn(urn).withdraw(selectedFarmUrn, amt);
emit Withdraw(urn, selectedFarmUrn, amt);
function withdraw(address urn, uint256 wad) external urnAuth(urn) {
address urnFarm = urnFarms[urn];
require(urnFarm != address(0), "LockstakeEngine/missing-selected-farm");
LockstakeUrn(urn).withdraw(urnFarm, wad);
emit Withdraw(urn, urnFarm, wad);
}

function getReward(address urn, address farm) external urnAuth(urn) {
LockstakeUrn(urn).getReward(farm, msg.sender);
emit GetReward(urn, farm);
function getReward(address urn, address farm, address to) external urnAuth(urn) {
uint256 amt = LockstakeUrn(urn).getReward(farm, to);
emit GetReward(urn, farm, to, amt);
}

// --- liquidation callback functions ---

function onKick(address urn, uint256 wad) external auth {
address selectedFarmUrn = selectedFarm[urn];
if (selectedFarmUrn != address(0)){
address urnFarm = urnFarms[urn];
if (urnFarm != address(0)){
uint256 freed = GemLike(stkGov).balanceOf(address(urn));
if (wad > freed) {
LockstakeUrn(urn).withdraw(selectedFarmUrn, wad - freed);
LockstakeUrn(urn).withdraw(urnFarm, wad - freed);
}
}
stkGov.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 NGT
DelegateLike(delegate_).free(wad); // Undelegate liquidated amount and retain the GOV tokens
}
// Urn confiscation happens in Dog contract where ilk vat.gem is sent to the LockstakeClipper
emit OnKick(urn, wad);
Expand Down
9 changes: 5 additions & 4 deletions src/LockstakeUrn.sol
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,14 @@ contract LockstakeUrn {
StakingRewardsLike(farm).stake(wad, ref);
}

function withdraw(address farm, uint256 amt) external isEngine{
StakingRewardsLike(farm).withdraw(amt);
function withdraw(address farm, uint256 wad) external isEngine {
StakingRewardsLike(farm).withdraw(wad);
}

function getReward(address farm, address usr) external isEngine {
function getReward(address farm, address to) external isEngine returns (uint256 amt) {
StakingRewardsLike(farm).getReward();
GemLike rewardsToken = StakingRewardsLike(farm).rewardsToken();
rewardsToken.transfer(usr, rewardsToken.balanceOf(address(this)));
amt = rewardsToken.balanceOf(address(this));
rewardsToken.transfer(to, amt);
}
}
41 changes: 25 additions & 16 deletions test/LockstakeEngine.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,14 @@ contract AllocatorVaultTest is DssTest {
event DelFarm(address farm);
event Open(address indexed owner, address urn);
event Lock(address indexed urn, uint256 wad);
event Free(address indexed urn, uint256 wad, uint256 burn);
event Free(address indexed urn, address indexed to, uint256 wad, uint256 burn);
event Delegate(address indexed urn, address indexed delegate_);
event Draw(address indexed urn, uint256 wad);
event Wipe(address indexed urn, uint256 wad);
event SelectFarm(address indexed urn, address farm);
event Stake(address indexed urn, address indexed farm, uint256 wad, uint16 ref);
event Withdraw(address indexed urn, address indexed farm, uint256 amt);
event GetReward(address indexed urn, address indexed farm);
event Withdraw(address indexed urn, address indexed farm, uint256 wad);
event GetReward(address indexed urn, address indexed farm, address indexed to, uint256 amt);
event OnKick(address indexed urn, uint256 wad);
event OnTake(address indexed urn, address indexed who, uint256 wad);
event OnTakeLeftovers(address indexed urn, uint256 tot, uint256 left, uint256 burn);
Expand Down Expand Up @@ -233,7 +233,7 @@ contract AllocatorVaultTest is DssTest {
gov.approve(address(engine), 100_000 * 10**18);
engine.lock(urn, 100_000 * 10**18);
assertEq(_ink(ilk, urn), 100_000 * 10**18);
engine.free(urn, 50_000 * 10**18);
engine.free(urn, address(this), 50_000 * 10**18);
assertEq(_ink(ilk, urn), 50_000 * 10**18);
engine.delegate(urn, voterDelegate);
assertEq(engine.urnDelegates(urn), voterDelegate);
Expand All @@ -250,7 +250,7 @@ contract AllocatorVaultTest is DssTest {
vm.expectRevert("LockstakeEngine/wad-overflow");
engine.lock(urn, uint256(type(int256).max) + 1);
vm.expectRevert("LockstakeEngine/wad-overflow");
engine.free(urn, uint256(type(int256).max) + 1);
engine.free(urn, address(this), uint256(type(int256).max) + 1);
if (withDelegate) {
engine.delegate(urn, voterDelegate);
}
Expand All @@ -271,18 +271,24 @@ contract AllocatorVaultTest is DssTest {
}
assertEq(gov.totalSupply(), initialSupply);
vm.expectEmit(true, true, true, true);
emit Free(urn, 40_000 * 10**18, 40_000 * 10**18 * 15 / 100);
engine.free(urn, 40_000 * 10**18);
emit Free(urn, address(this), 40_000 * 10**18, 40_000 * 10**18 * 15 / 100);
engine.free(urn, address(this), 40_000 * 10**18);
assertEq(_ink(ilk, urn), 60_000 * 10**18);
assertEq(stkGov.balanceOf(urn), 60_000 * 10**18);
assertEq(gov.balanceOf(address(this)), 40_000 * 10**18 - 40_000 * 10**18 * 15 / 100);
vm.expectEmit(true, true, true, true);
emit Free(urn, address(123), 10_000 * 10**18, 10_000 * 10**18 * 15 / 100);
engine.free(urn, address(123), 10_000 * 10**18);
assertEq(_ink(ilk, urn), 50_000 * 10**18);
assertEq(stkGov.balanceOf(urn), 50_000 * 10**18);
assertEq(gov.balanceOf(address(123)), 10_000 * 10**18 - 10_000 * 10**18 * 15 / 100);
if (withDelegate) {
assertEq(gov.balanceOf(address(engine)), 0);
assertEq(gov.balanceOf(address(voterDelegate)), 60_000 * 10**18);
assertEq(gov.balanceOf(address(voterDelegate)), 50_000 * 10**18);
} else {
assertEq(gov.balanceOf(address(engine)), 60_000 * 10**18);
assertEq(gov.balanceOf(address(engine)), 50_000 * 10**18);
}
assertEq(gov.totalSupply(), initialSupply - 40_000 * 10**18 * 15 / 100);
assertEq(gov.totalSupply(), initialSupply - 50_000 * 10**18 * 15 / 100);
}

function testLockFreeNoDelegate() public {
Expand Down Expand Up @@ -366,17 +372,17 @@ contract AllocatorVaultTest is DssTest {
function testSelectFarm() public {
StakingRewardsMock farm2 = new StakingRewardsMock(address(rTok), address(stkGov));
address urn = engine.open();
assertEq(engine.selectedFarm(urn), address(0));
assertEq(engine.urnFarms(urn), address(0));
vm.expectRevert("LockstakeEngine/non-existing-farm");
engine.selectFarm(urn, address(farm));
vm.prank(pauseProxy); engine.addFarm(address(farm));
vm.expectEmit(true, true, true, true);
emit SelectFarm(urn, address(farm));
engine.selectFarm(urn, address(farm));
assertEq(engine.selectedFarm(urn), address(farm));
assertEq(engine.urnFarms(urn), address(farm));
vm.prank(pauseProxy); engine.addFarm(address(farm2));
engine.selectFarm(urn, address(farm2));
assertEq(engine.selectedFarm(urn), address(farm2));
assertEq(engine.urnFarms(urn), address(farm2));
gov.approve(address(engine), 100_000 * 10**18);
engine.lock(urn, 100_000 * 10**18);
engine.stake(urn, 100_000, 1);
Expand Down Expand Up @@ -405,6 +411,9 @@ contract AllocatorVaultTest is DssTest {
assertEq(stkGov.balanceOf(address(urn)), 40_000 * 10**18);
assertEq(stkGov.balanceOf(address(farm)), 60_000 * 10**18);
assertEq(farm.balanceOf(address(urn)), 60_000 * 10**18);
vm.prank(pauseProxy); engine.delFarm(address(farm));
vm.expectRevert("LockstakeEngine/selected-farm-not-available-anymore");
engine.stake(urn, 10_000 * 10**18, 1);
vm.expectEmit(true, true, true, true);
emit Withdraw(urn, address(farm), 15_000 * 10**18);
engine.withdraw(urn, 15_000 * 10**18);
Expand All @@ -419,9 +428,9 @@ contract AllocatorVaultTest is DssTest {
farm.setReward(address(urn), 20_000);
assertEq(GemMock(address(farm.rewardsToken())).balanceOf(address(this)), 0);
vm.expectEmit(true, true, true, true);
emit GetReward(urn, address(farm));
engine.getReward(urn, address(farm));
assertEq(GemMock(address(farm.rewardsToken())).balanceOf(address(this)), 20_000);
emit GetReward(urn, address(farm), address(123), 20_000);
engine.getReward(urn, address(farm), address(123));
assertEq(GemMock(address(farm.rewardsToken())).balanceOf(address(123)), 20_000);
}

function _clipperSetUp(bool withDelegate) internal returns (address urn) {
Expand Down

0 comments on commit 6d76be6

Please sign in to comment.