Skip to content

Commit

Permalink
Merge pull request #65 from zodahu/feature/complete_comments
Browse files Browse the repository at this point in the history
Add natspec
  • Loading branch information
zodahu authored May 9, 2023
2 parents 0076796 + ef23c9e commit 971dbaa
Show file tree
Hide file tree
Showing 18 changed files with 170 additions and 47 deletions.
10 changes: 7 additions & 3 deletions src/Agent.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.0;

/// @title Agent executes arbitrary logics
/// @title A user's agent contract created by the router
/// @notice A proxy for delegating calls to the immutable agent implementation contract
contract Agent {
address internal immutable _implementation;

/// @dev Create an initialized agent
constructor(address implementation) {
_implementation = implementation;
(bool ok, ) = implementation.delegatecall(abi.encodeWithSignature('initialize()'));
Expand All @@ -13,12 +15,14 @@ contract Agent {

receive() external payable {}

/// @notice All the function will be delegated to `_implementation`
/// @notice Delegate all function calls to `_implementation`
fallback() external payable {
_delegate(_implementation);
}

/// @notice Referenced from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.1/contracts/proxy/Proxy.sol#L22
/// @notice Delegate the call to `_implementation`
/// @dev Referenced from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.1/contracts/proxy/Proxy.sol#L22
/// @param implementation The address of the implementation contract that this agent delegates calls to
function _delegate(address implementation) internal {
assembly {
// Copy msg.data. We take full control of memory in this inline assembly
Expand Down
21 changes: 18 additions & 3 deletions src/AgentImplementation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,29 @@ import {IRouter} from './interfaces/IRouter.sol';
import {IWrappedNative} from './interfaces/IWrappedNative.sol';
import {ApproveHelper} from './libraries/ApproveHelper.sol';

/// @title Implemtation contract of agent logics
/// @title Agent implementation contract
/// @notice Delegated by all users' agents
contract AgentImplementation is IAgent, ERC721Holder, ERC1155Holder {
using SafeERC20 for IERC20;
using Address for address;
using Address for address payable;

/// @dev Flag for identifying the native address such as ETH on Ethereum
address internal constant _NATIVE = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

/// @dev Denominator for calculating basis points
uint256 internal constant _BPS_BASE = 10_000;
uint256 internal constant _SKIP = 0x8000000000000000000000000000000000000000000000000000000000000000; // Equivalent to 1<<255, i.e. a singular 1 in the most significant bit.

/// @dev Flag for indicating a skipped value by setting the most significant bit to 1 (1<<255)
uint256 internal constant _SKIP = 0x8000000000000000000000000000000000000000000000000000000000000000;

/// @notice Immutable address for recording the router address
address public immutable router;

/// @notice Immutable address for recording wrapped native address such as WETH on Ethereum
address public immutable wrappedNative;

/// @notice Transient address for recording a valid caller which should be the router address after each execution
address internal _caller;

modifier checkCaller() {
Expand All @@ -37,17 +47,22 @@ contract AgentImplementation is IAgent, ERC721Holder, ERC1155Holder {
_;
}

/// @dev Create the agent implementation contract
constructor(address wrappedNative_) {
router = msg.sender;
wrappedNative = wrappedNative_;
}

/// @notice Initialize user's agent and can only be called once.
function initialize() external {
if (_caller != address(0)) revert Initialized();
_caller = router;
}

/// @notice Execute logics and return tokens to the current user
/// @notice Execute arbitrary logics
/// @param logics Array of logics to be executed
/// @param fees Array of fees
/// @param tokensReturn Array of ERC-20 tokens to be returned to the current user
function execute(
IParam.Logic[] calldata logics,
IParam.Fee[] calldata fees,
Expand Down
68 changes: 64 additions & 4 deletions src/Router.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,40 @@ import {IParam} from './interfaces/IParam.sol';
import {IRouter} from './interfaces/IRouter.sol';
import {LogicHash} from './libraries/LogicHash.sol';

/// @title Router executes arbitrary logics
/// @title Entry point for Composable Router
contract Router is IRouter, EIP712, FeeVerifier {
using SafeERC20 for IERC20;
using LogicHash for IParam.LogicBatch;
using SignatureChecker for address;

/// @dev Flag for reducing gas cost when reset `currentUser`
address internal constant _INIT_USER = address(1);

/// @dev Flag for identifying an invalid pauser address
address internal constant _INVALID_PAUSER = address(0);

/// @dev Flag for identifying an invalid fee collector address
address internal constant _INVALID_FEE_COLLECTOR = address(0);

/// @notice Immutable implementation contract for all users' agents
address public immutable agentImplementation;

/// @notice Mapping for recording exclusive agent contract to each user
mapping(address user => IAgent agent) public agents;

/// @notice Mapping for recording valid signers
mapping(address signer => bool valid) public signers;

/// @notice Transient address for recording `msg.sender` which resets to `_INIT_USER` after each execution
address public currentUser;

/// @notice Address for receiving collected fees
address public feeCollector;

/// @notice Address for invoking pause
address public pauser;

/// @notice Flag for indicating pause
bool public paused;

modifier checkCaller() {
Expand All @@ -50,30 +67,44 @@ contract Router is IRouter, EIP712, FeeVerifier {
_;
}

/// @dev Create the router with EIP-712 and the agent implementation contracts
constructor(address wrappedNative, address pauser_, address feeCollector_) EIP712('Composable Router', '1') {
currentUser = _INIT_USER;
agentImplementation = address(new AgentImplementation(wrappedNative));
_setPauser(pauser_);
_setFeeCollector(feeCollector_);
}

/// @notice Get owner address
/// @return The owner address
function owner() public view override(IRouter, Ownable) returns (address) {
return super.owner();
}

/// @notice Get domain separator used for EIP-712
/// @return The domain separator of Composable Router
function domainSeparator() external view returns (bytes32) {
return _domainSeparatorV4();
}

/// @notice Get agent address of a user
/// @param user The user address
/// @return The agent address of the user
function getAgent(address user) external view returns (address) {
return address(agents[user]);
}

/// @notice Get user and agent addresses of the current user
/// @return The user address
/// @return The agent address
function getUserAgent() external view returns (address, address) {
address user = currentUser;
return (user, address(agents[user]));
}

/// @notice Calculate agent address for a user using the CREATE2 formula
/// @param user The user address
/// @return The calculated agent address for the user
function calcAgent(address user) external view returns (address) {
address result = address(
uint160(
Expand All @@ -92,16 +123,22 @@ contract Router is IRouter, EIP712, FeeVerifier {
return result;
}

/// @notice Add a signer whose signature can pass the validation in `executeWithSignature` by owner
/// @param signer The signer address to be added
function addSigner(address signer) external onlyOwner {
signers[signer] = true;
emit SignerAdded(signer);
}

/// @notice Remove a signer by owner
/// @param signer The signer address to be removed
function removeSigner(address signer) external onlyOwner {
delete signers[signer];
emit SignerRemoved(signer);
}

/// @notice Set the fee collector address that collects fees from each user's agent by owner
/// @param feeCollector_ The fee collector address
function setFeeCollector(address feeCollector_) external onlyOwner {
_setFeeCollector(feeCollector_);
}
Expand All @@ -112,6 +149,8 @@ contract Router is IRouter, EIP712, FeeVerifier {
emit FeeCollectorSet(feeCollector_);
}

/// @notice Set the pauser address that can pause `execute` and `executeWithSignature` by owner
/// @param pauser_ The pauser address
function setPauser(address pauser_) external onlyOwner {
_setPauser(pauser_);
}
Expand All @@ -122,21 +161,32 @@ contract Router is IRouter, EIP712, FeeVerifier {
emit PauserSet(pauser_);
}

/// @notice Rescue ERC-20 tokens in case of stuck tokens by owner
/// @param token The token address
/// @param receiver The receiver address
/// @param amount The amount of tokens to be rescued
function rescue(address token, address receiver, uint256 amount) external onlyOwner {
IERC20(token).safeTransfer(receiver, amount);
}

/// @notice Pause `execute` and `executeWithSignature` by pauser
function pause() external onlyPauser {
paused = true;
emit Paused();
}

/// @notice Resume `execute` and `executeWithSignature` by pauser
function resume() external onlyPauser {
paused = false;
emit Resumed();
}

/// @notice Execute logics through current user's agent. Create agent for user if not created.
/// @notice Execute arbitrary logics through the current user's agent. Creates an agent for users if not created.
/// Charge fees based on the scenarios defined in the FeeVerifier contract, which calculates fees on-chain.
/// @param logics Array of logics to be executed
/// @param fees Array of fees
/// @param tokensReturn Array of ERC-20 tokens to be returned to the current user
/// @param referralCode Referral code
function execute(
IParam.Logic[] calldata logics,
IParam.Fee[] calldata fees,
Expand All @@ -156,7 +206,14 @@ contract Router is IRouter, EIP712, FeeVerifier {
agent.execute{value: msg.value}(logics, fees, tokensReturn);
}

/// @notice Execute logics with signer's signature.
/// @notice Execute arbitrary logics through the current user's agent using a signer's signature. Creates an agent
/// for users if not created. The fees in logicBatch are off-chain encoded, instead of being calculated by
/// the FeeVerifier contract.
/// @param logicBatch A struct containing logics, fees and deadline, signed by a signer using EIP-712
/// @param signer The signer address
/// @param signature The signer's signature bytes
/// @param tokensReturn Array of ERC-20 tokens to be returned to the current user
/// @param referralCode Referral code
function executeWithSignature(
IParam.LogicBatch calldata logicBatch,
address signer,
Expand All @@ -182,11 +239,14 @@ contract Router is IRouter, EIP712, FeeVerifier {
}

/// @notice Create an agent for `msg.sender`
/// @return The newly created agent address
function newAgent() external returns (address payable) {
return newAgent(msg.sender);
}

/// @notice Create an agent for `user`
/// @notice Create an agent for the user
/// @param user The user address
/// @return The newly created agent address
function newAgent(address user) public returns (address payable) {
if (address(agents[user]) != address(0)) {
revert AgentAlreadyCreated();
Expand Down
13 changes: 7 additions & 6 deletions src/callbacks/AaveV2FlashLoanCallback.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {IAaveV2Provider} from '../interfaces/aaveV2/IAaveV2Provider.sol';
import {ApproveHelper} from '../libraries/ApproveHelper.sol';

/// @title Aave V2 flash loan callback
/// @notice Invoked by Aave V2 pool to call the current user's agent
contract AaveV2FlashLoanCallback is IAaveV2FlashLoanCallback {
using SafeERC20 for IERC20;
using Address for address;
Expand All @@ -21,11 +22,11 @@ contract AaveV2FlashLoanCallback is IAaveV2FlashLoanCallback {
aaveV2Provider = aaveV2Provider_;
}

/// @dev No need to check whether `initiator` is Agent as it's certain when the below conditions are satisfied:
/// 1. `to` in Agent is Aave Pool, i.e, user signed a correct `to`
/// 2. `_callback` in Agent is set to this callback, i.e, user signed a correct `callback`
/// 3. `msg.sender` of this callback is Aave Pool
/// 4. Aave Pool contract is benign
/// @dev No need to check if `initiator` is the agent as it's certain when the below conditions are satisfied:
/// 1. The `to` address used in agent is Aave Pool, i.e, the user signed a correct `to`
/// 2. The `_callback` address set in agent is this callback, i.e, the user signed a correct `callback`
/// 3. The `msg.sender` of this callback is Aave Pool
/// 4. The Aave pool is benign
function executeOperation(
address[] calldata assets,
uint256[] calldata amounts,
Expand All @@ -38,7 +39,7 @@ contract AaveV2FlashLoanCallback is IAaveV2FlashLoanCallback {
if (msg.sender != pool) revert InvalidCaller();
(, address agent) = IRouter(router).getUserAgent();

// Transfer assets to Agent and record initial balances
// Transfer assets to the agent and record initial balances
uint256 assetsLength = assets.length;
uint256[] memory initBalances = new uint256[](assetsLength);
for (uint256 i = 0; i < assetsLength; ) {
Expand Down
13 changes: 7 additions & 6 deletions src/callbacks/AaveV3FlashLoanCallback.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {IAaveV3Provider} from '../interfaces/aaveV3/IAaveV3Provider.sol';
import {ApproveHelper} from '../libraries/ApproveHelper.sol';

/// @title Aave V3 flash loan callback
/// @notice Invoked by Aave V3 pool to call the current user's agent
contract AaveV3FlashLoanCallback is IAaveV3FlashLoanCallback {
using SafeERC20 for IERC20;
using Address for address;
Expand All @@ -21,11 +22,11 @@ contract AaveV3FlashLoanCallback is IAaveV3FlashLoanCallback {
aaveV3Provider = aaveV3Provider_;
}

/// @dev No need to check whether `initiator` is Agent as it's certain when the below conditions are satisfied:
/// 1. `to` in Agent is Aave Pool, i.e, user signed a correct `to`
/// 2. `_callback` in Agent is set to this callback, i.e, user signed a correct `callback`
/// 3. `msg.sender` of this callback is Aave Pool
/// 4. Aave Pool contract is benign
/// @dev No need to check if `initiator` is the agent as it's certain when the below conditions are satisfied:
/// 1. The `to` address used in agent is Aave Pool, i.e, the user signed a correct `to`
/// 2. The `_callback` address set in agent is this callback, i.e, the user signed a correct `callback`
/// 3. The `msg.sender` of this callback is Aave Pool
/// 4. The Aave pool is benign
function executeOperation(
address[] calldata assets,
uint256[] calldata amounts,
Expand All @@ -38,7 +39,7 @@ contract AaveV3FlashLoanCallback is IAaveV3FlashLoanCallback {
if (msg.sender != pool) revert InvalidCaller();
(, address agent) = IRouter(router).getUserAgent();

// Transfer assets to Agent and record initial balances
// Transfer assets to the agent and record initial balances
uint256 assetsLength = assets.length;
uint256[] memory initBalances = new uint256[](assetsLength);
for (uint256 i = 0; i < assetsLength; ) {
Expand Down
3 changes: 2 additions & 1 deletion src/callbacks/BalancerV2FlashLoanCallback.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {IRouter} from '../interfaces/IRouter.sol';
import {IBalancerV2FlashLoanCallback} from '../interfaces/IBalancerV2FlashLoanCallback.sol';

/// @title Balancer V2 flash loan callback
/// @notice Invoked by Balancer V2 vault to call the current user's agent
contract BalancerV2FlashLoanCallback is IBalancerV2FlashLoanCallback {
using SafeERC20 for IERC20;
using Address for address;
Expand All @@ -28,7 +29,7 @@ contract BalancerV2FlashLoanCallback is IBalancerV2FlashLoanCallback {
if (msg.sender != balancerV2Vault) revert InvalidCaller();
(, address agent) = IRouter(router).getUserAgent();

// Transfer tokens to Router and record initial balances
// Transfer assets to the agent and record initial balances
uint256 tokensLength = tokens.length;
uint256[] memory initBalances = new uint256[](tokensLength);
for (uint256 i = 0; i < tokensLength; ) {
Expand Down
1 change: 1 addition & 0 deletions src/fees/AaveBorrowFeeCalculator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {IAaveV3Provider} from '../interfaces/aaveV3/IAaveV3Provider.sol';
import {IFeeCalculator} from '../interfaces/IFeeCalculator.sol';
import {IParam} from '../interfaces/IParam.sol';

/// @title Aave borrow fee calculator
contract AaveBorrowFeeCalculator is IFeeCalculator, FeeCalculatorBase {
bytes32 internal constant _V2_BORROW_META_DATA = bytes32(bytes('aave-v2:borrow'));
bytes32 internal constant _V3_BORROW_META_DATA = bytes32(bytes('aave-v3:borrow'));
Expand Down
1 change: 1 addition & 0 deletions src/fees/AaveFlashLoanFeeCalculator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {IAaveV3Provider} from '../interfaces/aaveV3/IAaveV3Provider.sol';
import {IFeeCalculator} from '../interfaces/IFeeCalculator.sol';
import {IParam} from '../interfaces/IParam.sol';

/// @title Aave flash loan fee calculator
contract AaveFlashLoanFeeCalculator is IFeeCalculator, FeeCalculatorBase {
bytes32 internal constant _V2_FLASHLOAN_META_DATA = bytes32(bytes('aave-v2:flash-loan'));
bytes32 internal constant _V3_FLASHLOAN_META_DATA = bytes32(bytes('aave-v3:flash-loan'));
Expand Down
1 change: 1 addition & 0 deletions src/fees/BalancerFlashLoanFeeCalculator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {Router} from '../Router.sol';
import {IFeeCalculator} from '../interfaces/IFeeCalculator.sol';
import {IParam} from '../interfaces/IParam.sol';

/// @title Balancer flash loan fee calculator
contract BalancerFlashLoanFeeCalculator is IFeeCalculator, FeeCalculatorBase {
bytes32 internal constant _META_DATA = bytes32(bytes('balancer-v2:flash-loan'));

Expand Down
Loading

0 comments on commit 971dbaa

Please sign in to comment.