From fae51fb82c518e1b0cbb238c1a257b2feeaf9982 Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Wed, 13 Dec 2023 13:44:46 -0300 Subject: [PATCH] Allow other address to do urn management (#5) * 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> --- src/LockstakeEngine.sol | 64 ++++++++++++++++++++++++-------------- test/LockstakeEngine.t.sol | 60 ++++++++++++++++++++++++++++------- 2 files changed, 90 insertions(+), 34 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 36a0326c..24bbc293 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -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 --- @@ -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); @@ -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"); _; } @@ -133,7 +136,7 @@ contract LockstakeEngine { emit Rely(msg.sender); } - // --- math --- + // --- internals --- function _divup(uint256 x, uint256 y) internal pure returns (uint256 z) { unchecked { @@ -141,6 +144,10 @@ contract LockstakeEngine { } } + 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 { @@ -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( @@ -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) { @@ -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]; @@ -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); @@ -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"); @@ -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"); @@ -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); @@ -266,7 +284,7 @@ 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"); @@ -274,7 +292,7 @@ contract LockstakeEngine { 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"); @@ -282,14 +300,14 @@ contract LockstakeEngine { 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); } diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 2927572a..1e205ff8 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -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(); } @@ -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);