I am trying to solve a circular dependency in a "toy" app to test upgradable contracts.
I have a Treasury contract containing ERC20 tokens and an upgradable set of Action contracts. The Action contracts interact with other DeFi building blocks like staking on Yearn, swapping on Uniswap, buying bonds on Olympus etc. but the contract itself does not possess any token. Now, to save on gas I'd like to avoid transferring tokens from the Treasury to the Action contracts and then to the external DeFi protocol so I've come up with a possible solutions to that.
Have a special function in the treasury restricted to action contracts only that calls the action back via a delegate call from the Treasury, thus giving full control over the funds.
A scheme
User -> Action -> Treasury -> (delegatecall) Action -> external DeFi contract
inside the Treasury
function relayCall(address to, bytes calldata data) external onlyActions {
require(isActionContract[to], "Err: can only call an action");
(success,) = to.delegatecall(data);
require(success);
}
inside the Action
function makeCall(...) external {
bytes memory data = abi.encodeWithSelector(Action.swap.selector, ...);
ITreasury(treasury).relayCall(address(this), data);
}
function swap(address token0, address token1, address recipient, uint256 amount) external {
uniRouter.swapExactTokensForTokens(...);
}
The pros of this approach are that it is compatible with an upgradable Action contract it is quite safe as the recipient is whitelisted and the delegate call scope is fixed in the Action code, the big con is the circular dependency Action -> Treasury -> Action
.