Skip to content

Commit

Permalink
Allow other address to do urn management (#5)
Browse files Browse the repository at this point in the history
* Allow urn management to another address

* Add tests

* Add isUrnAuth getter

* Remove README

* Extend testHopeNope

* Remove line

Co-authored-by: oldchili <130549691+oldchili@users.noreply.github.com>

---------

Co-authored-by: oldchili <130549691+oldchili@users.noreply.github.com>
  • Loading branch information
sunbreak1211 and oldchili authored Dec 13, 2023
1 parent c7fa375 commit fae51fb
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 34 deletions.
64 changes: 41 additions & 23 deletions src/LockstakeEngine.sol
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,14 @@ interface JugLike {
contract LockstakeEngine {
// --- storage variables ---

mapping(address => uint256) public wards; // usr => 1 == access
mapping(address => uint256) public farms; // farm => 1 == whitelisted
mapping(address => uint256) public usrAmts; // usr => urns amount
mapping(address => address) public urnOwners; // urn => owner
mapping(address => address) public urnDelegates; // urn => current associated delegate
mapping(address => address) public urnFarms; // urn => current selected farm
JugLike public jug;
mapping(address => uint256) public wards; // usr => 1 == access
mapping(address => uint256) public farms; // farm => 1 == whitelisted
mapping(address => uint256) public usrAmts; // usr => urns amount
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 urnFarms; // urn => current selected farm
JugLike public jug;

// --- constants ---

Expand All @@ -90,6 +91,8 @@ contract LockstakeEngine {
event AddFarm(address farm);
event DelFarm(address farm);
event Open(address indexed owner, address urn);
event Hope(address indexed urn, address indexed usr);
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, address indexed to, uint256 wad, uint256 burn);
Expand All @@ -111,8 +114,8 @@ contract LockstakeEngine {
_;
}

modifier urnOwner(address urn) {
require(urnOwners[urn] == msg.sender, "LockstakeEngine/not-urn-owner");
modifier urnAuth(address urn) {
require(_urnAuth(urn, msg.sender), "LockstakeEngine/urn-not-authorized");
_;
}

Expand All @@ -133,14 +136,18 @@ contract LockstakeEngine {
emit Rely(msg.sender);
}

// --- math ---
// --- internals ---

function _divup(uint256 x, uint256 y) internal pure returns (uint256 z) {
unchecked {
z = x != 0 ? ((x - 1) / y) + 1 : 0;
}
}

function _urnAuth(address urn, address usr) internal view returns (bool ok) {
ok = urnOwners[urn] == usr || urnCan[urn][usr] == 1;
}

// --- administration ---

function rely(address usr) external auth {
Expand Down Expand Up @@ -172,10 +179,7 @@ contract LockstakeEngine {

// --- getters ---

function getUrn(
address owner,
uint256 index
) external view returns (address urn) {
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)));
urn = address(uint160(uint256(
Expand All @@ -185,6 +189,10 @@ 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) {
Expand All @@ -194,7 +202,17 @@ contract LockstakeEngine {
emit Open(msg.sender, urn);
}

function lock(address urn, uint256 wad) external urnOwner(urn) {
function hope(address urn, address usr) external urnAuth(urn) {
urnCan[urn][usr] = 1;
emit Hope(urn, usr);
}

function nope(address urn, address usr) external urnAuth(urn) {
urnCan[urn][usr] = 0;
emit Nope(urn, usr);
}

function lock(address urn, uint256 wad) external urnAuth(urn) {
require(wad <= uint256(type(int256).max), "LockstakeEngine/wad-overflow");
gov.transferFrom(msg.sender, address(this), wad);
address delegate_ = urnDelegates[urn];
Expand All @@ -210,7 +228,7 @@ contract LockstakeEngine {
emit Lock(urn, wad);
}

function free(address urn, address to, uint256 wad) external urnOwner(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);
Expand All @@ -225,7 +243,7 @@ contract LockstakeEngine {
emit Free(urn, to, wad, burn);
}

function delegate(address urn, address delegate_) external urnOwner(urn) {
function delegate(address urn, address delegate_) external urnAuth(urn) {
require(delegate_ == address(0) || delegateFactory.isDelegate(delegate_) == 1, "LockstakeEngine/not-valid-delegate");
address prevDelegate = urnDelegates[urn];
require(prevDelegate != delegate_, "LockstakeEngine/same-delegate");
Expand All @@ -245,7 +263,7 @@ contract LockstakeEngine {

// --- loan functions ---

function draw(address urn, uint256 wad) external urnOwner(urn) {
function draw(address urn, uint256 wad) external urnAuth(urn) {
uint256 rate = jug.drip(ilk);
uint256 dart = _divup(wad * RAY, rate);
require(dart <= uint256(type(int256).max), "LockstakeEngine/overflow");
Expand All @@ -254,7 +272,7 @@ contract LockstakeEngine {
emit Draw(urn, wad);
}

function wipe(address urn, uint256 wad) external urnOwner(urn) {
function wipe(address urn, uint256 wad) external urnAuth(urn) {
nst.transferFrom(msg.sender, address(this), wad);
nstJoin.join(address(this), wad);
uint256 rate = jug.drip(ilk);
Expand All @@ -266,30 +284,30 @@ contract LockstakeEngine {

// --- staking functions ---

function selectFarm(address urn, address farm) external urnOwner(urn) {
function selectFarm(address urn, address farm) external urnAuth(urn) {
require(farms[farm] == 1, "LockstakeEngine/non-existing-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 urnOwner(urn) {
function stake(address urn, uint256 wad, uint16 ref) external urnAuth(urn) {
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 wad) external urnOwner(urn) {
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, address to) external urnOwner(urn) {
function getReward(address urn, address farm, address to) external urnAuth(urn) {
uint256 amt = LockstakeUrn(urn).getReward(farm, to);
emit GetReward(urn, farm, to, amt);
}
Expand Down
60 changes: 49 additions & 11 deletions test/LockstakeEngine.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -169,19 +169,21 @@ contract AllocatorVaultTest is DssTest {
checkModifier(address(engine), "LockstakeEngine/not-authorized", authedMethods);
vm.stopPrank();

bytes4[] memory urnOwnersMethods = new bytes4[](9);
urnOwnersMethods[0] = engine.lock.selector;
urnOwnersMethods[1] = engine.free.selector;
urnOwnersMethods[2] = engine.delegate.selector;
urnOwnersMethods[3] = engine.draw.selector;
urnOwnersMethods[4] = engine.wipe.selector;
urnOwnersMethods[5] = engine.selectFarm.selector;
urnOwnersMethods[6] = engine.stake.selector;
urnOwnersMethods[7] = engine.withdraw.selector;
urnOwnersMethods[8] = engine.getReward.selector;
bytes4[] memory urnOwnersMethods = new bytes4[](11);
urnOwnersMethods[0] = engine.hope.selector;
urnOwnersMethods[1] = engine.nope.selector;
urnOwnersMethods[2] = engine.lock.selector;
urnOwnersMethods[3] = engine.free.selector;
urnOwnersMethods[4] = engine.delegate.selector;
urnOwnersMethods[5] = engine.draw.selector;
urnOwnersMethods[6] = engine.wipe.selector;
urnOwnersMethods[7] = engine.selectFarm.selector;
urnOwnersMethods[8] = engine.stake.selector;
urnOwnersMethods[9] = engine.withdraw.selector;
urnOwnersMethods[10] = engine.getReward.selector;

vm.startPrank(address(0xBEEF));
checkModifier(address(engine), "LockstakeEngine/not-urn-owner", urnOwnersMethods);
checkModifier(address(engine), "LockstakeEngine/urn-not-authorized", urnOwnersMethods);
vm.stopPrank();
}

Expand Down Expand Up @@ -214,6 +216,42 @@ contract AllocatorVaultTest is DssTest {
assertEq(engine.usrAmts(address(this)), 3);
}

function testHopeNope() public {
address urnOwner = address(123);
address urnAuthed = address(456);
vm.prank(pauseProxy); engine.addFarm(address(farm));
gov.transfer(urnAuthed, 100_000 * 10**18);
vm.startPrank(urnOwner);
address urn = engine.open();
assertTrue(engine.isUrnAuth(urn, urnOwner));
assertTrue(!engine.isUrnAuth(urn, urnAuthed));
assertEq(engine.urnCan(urn, urnAuthed), 0);
engine.hope(urn, urnAuthed);
assertEq(engine.urnCan(urn, urnAuthed), 1);
assertTrue(engine.isUrnAuth(urn, urnAuthed));
vm.stopPrank();
vm.startPrank(urnAuthed);
engine.hope(urn, address(789));
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, address(this), 50_000 * 10**18);
assertEq(_ink(ilk, urn), 50_000 * 10**18);
engine.delegate(urn, voterDelegate);
assertEq(engine.urnDelegates(urn), voterDelegate);
engine.draw(urn, 1);
nst.approve(address(engine), 1);
engine.wipe(urn, 1);
engine.selectFarm(urn, address(farm));
engine.stake(urn, 1, 0);
engine.withdraw(urn, 1);
engine.getReward(urn, address(farm), address(0));
engine.nope(urn, urnAuthed);
assertEq(engine.urnCan(urn, urnAuthed), 0);
assertTrue(!engine.isUrnAuth(urn, urnAuthed));
vm.stopPrank();
}

function _testLockFree(bool withDelegate) internal {
uint256 initialSupply = gov.totalSupply();
assertEq(gov.balanceOf(address(this)), 100_000 * 10**18);
Expand Down

0 comments on commit fae51fb

Please sign in to comment.