2

We have a contract that adheres to the ERC-3156 standard, which provides flash loan functionality. We intend to deploy this contract multiple times, and in certain cases, flash loan capabilities are not required.

One possible solution is to implement a paused flag. When set to true, this flag would deactivate the flashLoan function. But this approach seems inefficient, as it retains the unnecessary ERC-3156 features in contracts that will never use them.

Is there a more elegant method for selectively including logic in a contract during its construction?

This is what I have in mind:

contract Foo {
    constructor(bool flag) {
        if (flag) {
            // include ERC-3156 logic
        } else {
            // do not include ERC-3156 logic
        }
    }
}

Notes:

  • I don't want two different contract names
  • By "not including", I mean for the flashLoan function to not exist
  • I am deploying the contracts with Foundry
4
  • Hey! how you are deploying the contracts, from a smart contract or hardhat/ganache, etc..?
    – donoso.eth
    Apr 5, 2023 at 7:30
  • Good question. I am using Foundry. Apr 5, 2023 at 7:39
  • 2
    Based on the provided contract's logic, i don't think there is a way to remove the flashloan logic. By setting the flag in the constructor, you are only allowing and disallowing the flashloan logic, but not actually preventing it to exist in the contract. For me personally, the best solution will be to have 2 different contracts with and without flashloan logic, so you can deploy each one wherever needed. You can change the File.sol name but the contract name inside can stay the same.
    – Akall
    Apr 5, 2023 at 7:39
  • All great points, thanks @Akall. I didn't think about keeping the contract names the same but naming the files differently. Apr 5, 2023 at 8:19

1 Answer 1

1

You can't partially self-destruct the contract. When the constructor runs, the runtime bytecode is already on-chain and you can already call arbitrary contracts method. You have some other clean ways to handle this:

1. Module/Library/Proxy

There are different ways to create modular contracts by using proxy patterns, like Transparent Proxy, Delegate Proxy, Diamonds, etc. But the main idea - is to deploy multiple contracts. The implementation one (FlashLoan logic) and the main contract, which in some way forwards requests to the underlying modules. So when the contract doesn't have a FlashLoan capability, the address of the module stays unset.

interface IModule {
    function setFoo(uint256 foo) external;
}

contract FooModule is IModule {
    uint256 public foo;

    function setFoo (uint256 foo) external {
        // implementation
    }
}

contract Foo {
    uint public foo;
    IModule module;

    constructor (address _module) {
        module = IModule(_module);
    }
    fallback() external payable {
        address target = address(module);
        require(target != address(0));
        assembly {
            let ptr := mload(0x40)
            calldatacopy(ptr, 0, calldatasize())
            let result := delegatecall(gas(), target, ptr, calldatasize(), 0, 0)
            let size := returndatasize()
            returndatacopy(ptr, 0, size)

            switch result
            case 0 { revert(ptr, size) }
            default { return(ptr, size) }
        }
    }
}

2. Inheritance

You would have a class with base logic and another one, which adds the FlashLoan methods.

contract A {
    function a () external view returns (uint256) {
        return 1;
    }
}
contract B is A {
    function b () external view returns (uint256) {
        return 2;
    }
}

Later, based on the business case you deploy the A contract or the B contract.

3
  • Great answer, but why bother with low-level assembly for the delegate call? High-level Solidity should work well for most users Apr 5, 2023 at 11:33
  • This is a typical Transparent Proxy - we need assembly calldatacopy to pass arbitrary method sig and the method's arguments to the underlying implementation - without assembly, it is not possible. High-level address(imp).delegatecall(abi.encodeWithSelector(selecto)); can be used if you know the arguments. You can surely use any other type of module structuring.
    – tenbits
    Apr 5, 2023 at 11:53
  • Oh, just though you can indeed use the (bool success, ) = imp.delegatecall(msg.data); :)
    – tenbits
    Apr 5, 2023 at 12:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.