diff --git a/src/gnosissafe/GnosisSafeWrapperFactory.sol b/src/gnosissafe/GnosisSafeWrapperFactory.sol index 2bf7e16..2d86cdb 100644 --- a/src/gnosissafe/GnosisSafeWrapperFactory.sol +++ b/src/gnosissafe/GnosisSafeWrapperFactory.sol @@ -6,44 +6,69 @@ import { Clones } from "@openzeppelin/contracts/proxy/Clones.sol"; import { GnosisSafeWrapper } from "./GnosisSafeWrapper.sol"; contract GnosisSafeWrapperFactory { - event LenderCreated(address indexed safe, GnosisSafeWrapper lender); + event LenderCreated(address indexed safe, GnosisSafeWrapper _lender); event LendingDataSet(address indexed safe, address indexed asset, uint248 fee, bool enabled); address public constant ALL_ASSETS = address(0); GnosisSafeWrapper public immutable template; - mapping(address safe => GnosisSafeWrapper lender) public lenders; - constructor() { template = new GnosisSafeWrapper(); } - function _deploy(address safe) internal returns (GnosisSafeWrapper lender) { - lender = GnosisSafeWrapper(Clones.cloneDeterministic(address(template), bytes20(safe))); - lender.initialize(safe); - lenders[safe] = lender; - emit LenderCreated(safe, lender); + /// @dev Returns true if `_lender` is a contract. + /// @param _lender The address being checked. + // This method relies on extcodesize, which returns 0 for contracts in + // construction, since the code is only stored at the end of the + // constructor execution. + function _deployed(GnosisSafeWrapper _lender) internal view returns (bool) { + return address(_lender).code.length > 0; } - function deploy(address safe) public returns (GnosisSafeWrapper lender) { - lender = _deploy(safe); + /// @dev Deploy a new Gnosis Safe wrapper for a Gnosis Safe. + /// The factory will become the owner of the wrapper, and the safe will be able to govern the wrapper through the factory. + /// There can ever be only one wrapper per safe + /// @param safe Address of the Gnosis Safe. + function _deploy(address safe) internal returns (GnosisSafeWrapper _lender) { + _lender = GnosisSafeWrapper(Clones.cloneDeterministic(address(template), bytes20(safe))); + _lender.initialize(safe); + emit LenderCreated(safe, _lender); } - function predictLenderAddress(address safe) public view returns (address lender) { - lender = Clones.predictDeterministicAddress(address(template), bytes20(safe)); + function _getOrDeploy(address safe) internal returns (GnosisSafeWrapper _lender) { + _lender = lender(safe); + if (!_deployed(_lender)) _lender = _deploy(safe); } - function myLender() public view returns (address lender) { - lender = Clones.predictDeterministicAddress(address(template), bytes20(msg.sender)); + /// @dev Deploy a new Gnosis Safe wrapper for a Gnosis Safe. + /// @param safe Address of the Gnosis Safe. + function deploy(address safe) public returns (GnosisSafeWrapper _lender) { + _lender = _deploy(safe); } - function lending(address asset) public view returns (uint248 fee, bool enabled) { - return lenders[msg.sender].lending(asset); + /// @dev Get the Gnosis Safe wrapper for a Gnosis Safe. + /// @param safe Address of the Gnosis Safe. + function lender(address safe) public view returns (GnosisSafeWrapper _lender) { + _lender = GnosisSafeWrapper(Clones.predictDeterministicAddress(address(template), bytes20(safe))); } + /// @dev Get the Gnosis Safe wrapper for the sender. + function lender() public view returns (GnosisSafeWrapper _lender) { + _lender = lender(msg.sender); + } + + /// @dev Get the lending data for a Gnosis Safe and asset. + /// @param safe Address of the Gnosis Safe. + /// @param asset Address of the asset. function lending(address safe, address asset) public view returns (uint248 fee, bool enabled) { - return lenders[safe].lending(asset); + return lender(safe).lending(asset); + } + + /// @dev Get the lending data for an asset for the sender. + /// @param asset Address of the asset. + function lending(address asset) public view returns (uint248 fee, bool enabled) { + return lending(msg.sender, asset); } /// @dev Set lending data for an asset. @@ -51,9 +76,8 @@ contract GnosisSafeWrapperFactory { /// @param fee Fee for the flash loan (FP 1e-4) /// @param enabled Whether the asset is enabled for flash loans. function lend(address asset, uint248 fee, bool enabled) public { - GnosisSafeWrapper lender = lenders[msg.sender]; - if (lender == GnosisSafeWrapper(address(0))) lender = _deploy(msg.sender); - lender.lend(asset, fee, enabled); + GnosisSafeWrapper _lender = _getOrDeploy(msg.sender); + _lender.lend(asset, fee, enabled); emit LendingDataSet(msg.sender, asset, fee, enabled); } @@ -61,9 +85,8 @@ contract GnosisSafeWrapperFactory { /// @param fee Fee for the flash loan (FP 1e-4) /// @param enabled Whether the lending data override is enabled for flash loans. function lendAll(uint248 fee, bool enabled) public { - GnosisSafeWrapper lender = lenders[msg.sender]; - if (lender == GnosisSafeWrapper(address(0))) lender = _deploy(msg.sender); - lender.lendAll(fee, enabled); - emit LendingDataSet(msg.sender, address(0), fee, enabled); + GnosisSafeWrapper _lender = _getOrDeploy(msg.sender); + _lender.lendAll(fee, enabled); + emit LendingDataSet(msg.sender, ALL_ASSETS, fee, enabled); } } diff --git a/test/GnosisSafeWrapper.t.sol b/test/GnosisSafeWrapper.t.sol index e3e7497..8cc6a0b 100644 --- a/test/GnosisSafeWrapper.t.sol +++ b/test/GnosisSafeWrapper.t.sol @@ -27,6 +27,10 @@ abstract contract GnosisSafeWrapperStateZero is Test { address internal USDC; IGnosisSafe internal safe; + function _deployed(GnosisSafeWrapper _lender) internal view returns (bool) { + return address(_lender).code.length > 0; + } + /// @dev A function invoked before each test case is run. function setUp() public virtual { // Revert if there is no API key. @@ -49,21 +53,20 @@ contract GnosisSafeWrapperStateZeroTest is GnosisSafeWrapperStateZero { function test_deploy() external { console2.log("test_deploy"); wrapper = factory.deploy(address(safe)); - assertEq(address(factory.lenders(address(safe))), address(wrapper)); assertEq(address(wrapper.safe()), address(safe)); } - function test_predictLenderAddressDebug() external { - console2.log("test_predictLenderAddress"); + function test_lender() external { + console2.log("test_lender"); wrapper = factory.deploy(address(safe)); - assertEq(factory.predictLenderAddress(address(safe)), address(wrapper)); + assertEq(address(factory.lender(address(safe))), address(wrapper)); } function test_lendDebug() external { console2.log("test_lend"); vm.prank(address(safe)); factory.lend(USDT, 10, true); - wrapper = GnosisSafeWrapper(factory.predictLenderAddress(address(safe))); + wrapper = factory.lender(address(safe)); (uint256 fee, bool enabled) = wrapper.lending(USDT); assertEq(fee, 10); assertEq(enabled, true); @@ -73,17 +76,17 @@ contract GnosisSafeWrapperStateZeroTest is GnosisSafeWrapperStateZero { console2.log("test_lendAll"); vm.prank(address(safe)); factory.lendAll(10, true); - wrapper = GnosisSafeWrapper(factory.predictLenderAddress(address(safe))); + wrapper = factory.lender(address(safe)); (uint256 fee, bool enabled) = wrapper.lending(wrapper.ALL_ASSETS()); assertEq(fee, 10); assertEq(enabled, true); } - function test_myLender() external { - console2.log("test_myLender"); + function test_lenderNoParams() external { + console2.log("test_lenderNoParams"); vm.startPrank(address(safe)); wrapper = factory.deploy(address(safe)); - assertEq(factory.myLender(), address(wrapper)); + assertEq(address(factory.lender()), address(wrapper)); vm.stopPrank(); } } @@ -93,7 +96,7 @@ abstract contract GnosisSafeWrapperWithWrapper is GnosisSafeWrapperStateZero { super.setUp(); vm.startPrank(address(safe)); - wrapper = GnosisSafeWrapper(factory.myLender()); + wrapper = factory.lender(); safe.enableModule(address(wrapper)); factory.lend(USDT, 10, true); vm.stopPrank();