From 5f6051be55c7869b6dde63db887b64074e050fd8 Mon Sep 17 00:00:00 2001 From: telome <130504305+telome@users.noreply.github.com> Date: Tue, 9 Jan 2024 12:00:16 +0000 Subject: [PATCH] Use EIP1167 for urn (#8) * 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 * Use EIP1167 for urn * Fix rebase conflict * Minor formatting * Small _initCode reformatting * Remove unnecessary create2 success check * Add LockstakeUrn init and ctor tests * FIx vat.can interface --------- Co-authored-by: oldchili <130549691+oldchili@users.noreply.github.com> Co-authored-by: sunbreak Co-authored-by: telome <> --- src/LockstakeEngine.sol | 21 ++++++++++++++++++--- src/LockstakeUrn.sol | 9 +++++++-- test/LockstakeEngine.t.sol | 14 ++++++++++++++ 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 9f02e405..85d62d96 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -92,6 +92,7 @@ contract LockstakeEngine is Multicall { MkrNgtLike immutable public mkrNgt; GemLike immutable public ngt; uint256 immutable public mkrNgtRate; + address immutable public urnImplementation; // --- events --- @@ -149,6 +150,7 @@ contract LockstakeEngine is Multicall { ngt.approve(address(mkrNgt), type(uint256).max); mkr.approve(address(mkrNgt), type(uint256).max); mkrNgtRate = mkrNgt.rate(); + urnImplementation = address(new LockstakeUrn(address(vat), stkMkr_)); wards[msg.sender] = 1; emit Rely(msg.sender); @@ -166,6 +168,17 @@ contract LockstakeEngine is Multicall { ok = urnOwners[urn] == usr || urnCan[urn][usr] == 1; } + // See the reference implementation in https://eips.ethereum.org/EIPS/eip-1167 + function _initCode() internal view returns (bytes memory code) { + code = new bytes(0x37); + bytes20 impl = bytes20(urnImplementation); + assembly { + mstore(add(code, 0x20), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000) + mstore(add(code, add(0x20, 0x14)), impl) + mstore(add(code, add(0x20, 0x28)), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000) + } + } + // --- administration --- function rely(address usr) external auth { @@ -199,7 +212,7 @@ contract LockstakeEngine is Multicall { 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, stkMkr))); + bytes32 codeHash = keccak256(abi.encodePacked(_initCode())); urn = address(uint160(uint256( keccak256( abi.encodePacked(bytes1(0xff), address(this), salt, codeHash) @@ -215,8 +228,10 @@ contract LockstakeEngine is Multicall { 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))); + uint256 salt = uint256(keccak256(abi.encode(msg.sender, index))); + bytes memory initCode = _initCode(); + assembly { urn := create2(0, add(initCode, 0x20), 0x37, salt) } + LockstakeUrn(urn).init(); // would revert if create2 had failed urnOwners[urn] = msg.sender; emit Open(msg.sender, urn); } diff --git a/src/LockstakeUrn.sol b/src/LockstakeUrn.sol index 622d0f6a..9a09117b 100644 --- a/src/LockstakeUrn.sol +++ b/src/LockstakeUrn.sol @@ -38,6 +38,7 @@ contract LockstakeUrn { address immutable public engine; GemLike immutable public stkMkr; + VatLike immutable public vat; // --- modifiers --- @@ -46,12 +47,16 @@ contract LockstakeUrn { _; } - // --- constructor --- + // --- constructor & init --- constructor(address vat_, address stkMkr_) { engine = msg.sender; + vat = VatLike(vat_); stkMkr = GemLike(stkMkr_); - VatLike(vat_).hope(msg.sender); + } + + function init() external isEngine { + vat.hope(msg.sender); stkMkr.approve(msg.sender, type(uint256).max); } diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index f0d3bde2..3fbc6b2f 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.16; import "dss-test/DssTest.sol"; import { LockstakeEngine } from "src/LockstakeEngine.sol"; import { LockstakeClipper } from "src/LockstakeClipper.sol"; +import { LockstakeUrn } from "src/LockstakeUrn.sol"; import { PipMock } from "test/mocks/PipMock.sol"; import { DelegateFactoryMock, DelegateMock } from "test/mocks/DelegateMock.sol"; import { GemMock } from "test/mocks/GemMock.sol"; @@ -18,6 +19,7 @@ interface ChainlogLike { } interface VatLike { + function can(address, address) external view returns (uint256); function dai(address) external view returns (uint256); function gem(bytes32, address) external view returns (uint256); function ilks(bytes32) external view returns (uint256, uint256, uint256, uint256, uint256); @@ -214,12 +216,24 @@ contract LockstakeEngineTest is DssTest { address urn = engine.getUrn(address(this), 0); vm.expectRevert("LockstakeEngine/wrong-urn-index"); engine.open(1); + + assertEq(VatLike(vat).can(urn, address(engine)), 0); + assertEq(stkMkr.allowance(urn, address(engine)), 0); vm.expectEmit(true, true, true, true); emit Open(address(this), urn); assertEq(engine.open(0), urn); assertEq(engine.usrAmts(address(this)), 1); + assertEq(VatLike(vat).can(urn, address(engine)), 1); + assertEq(stkMkr.allowance(urn, address(engine)), type(uint256).max); + assertEq(LockstakeUrn(urn).engine(), address(engine)); + assertEq(address(LockstakeUrn(urn).stkMkr()), address(stkMkr)); + assertEq(address(LockstakeUrn(urn).vat()), vat); + vm.expectRevert("LockstakeUrn/not-engine"); + LockstakeUrn(urn).init(); + vm.expectRevert("LockstakeEngine/wrong-urn-index"); engine.open(2); + assertEq(engine.getUrn(address(this), 1), engine.open(1)); assertEq(engine.usrAmts(address(this)), 2); assertEq(engine.getUrn(address(this), 2), engine.open(2));