Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion packages/evm/contracts/Intents.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,22 @@

pragma solidity ^0.8.20;

import './dynamic-calls/DynamicCallTypes.sol';

/**
* @dev Enum representing the operation type.
* - Swap: Swap tokens in the same chain.
* - Transfer: Transfer tokens to one or more recipients.
* - Call: Execute arbitrary contract calls.
* - CrossChainSwap: Swap tokens between chains.
* - DynamicCall: Execute arbitrary dynamic contract calls.
*/
enum OpType {
Swap,
Transfer,
Call,
CrossChainSwap
CrossChainSwap,
DynamicCall
}

/**
Expand Down Expand Up @@ -162,6 +166,16 @@ struct CallData {
uint256 value;
}

/**
* @dev Represents a generic dynamic call operation consisting of one or more dynamic contract calls.
* @param chainId Chain ID where the calls should be executed.
* @param calls List of ABI-encoded low-level dynamic contract calls to be executed.
*/
struct DynamicCallOperation {
uint256 chainId;
bytes[] calls;
}

/**
* @dev Generic proposal structure representing a solver’s response to an intent.
* @param deadline Timestamp until when the proposal is valid.
Expand Down
128 changes: 113 additions & 15 deletions packages/evm/contracts/Settler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ import '@openzeppelin/contracts/utils/cryptography/EIP712.sol';
import '@openzeppelin/contracts/utils/introspection/ERC165Checker.sol';

import './Intents.sol';
import './dynamic-calls/DynamicCallEncoder.sol';
import './interfaces/IController.sol';
import './interfaces/IDynamicCallEncoder.sol';
import './interfaces/IOperationsValidator.sol';
import './interfaces/IExecutor.sol';
import './interfaces/ISettler.sol';
Expand Down Expand Up @@ -53,6 +55,9 @@ contract Settler is ISettler, Ownable, ReentrancyGuard, EIP712 {
// Operations validator reference
address public override operationsValidator;

// Dynamic call encoder reference
address public dynamicCallEncoder;

// List of block numbers at which an intent was executed
mapping (bytes32 => uint256) public override getIntentBlock;

Expand All @@ -76,6 +81,7 @@ contract Settler is ISettler, Ownable, ReentrancyGuard, EIP712 {
constructor(address _controller, address _owner) Ownable(_owner) EIP712('Mimic Protocol Settler', '1') {
controller = _controller;
smartAccountsHandler = address(new SmartAccountsHandler());
dynamicCallEncoder = address(new DynamicCallEncoder());
}

/**
Expand Down Expand Up @@ -145,6 +151,14 @@ contract Settler is ISettler, Ownable, ReentrancyGuard, EIP712 {
_setOperationsValidator(newOperationsValidator);
}

/**
* @dev Sets a new dynamic call encoder address
* @param newDynamicCallEncoder New dynamic call encoder to be set
*/
function setDynamicCallEncoder(address newDynamicCallEncoder) external override onlyOwner {
_setDynamicCallEncoder(newDynamicCallEncoder);
}

/**
* @dev Sets a safeguard for a user
* @param safeguard Safeguard to be set
Expand Down Expand Up @@ -199,25 +213,46 @@ contract Settler is ISettler, Ownable, ReentrancyGuard, EIP712 {
_validateIntent(intent, proposal, signature, simulated);
getIntentBlock[intent.hash()] = block.number;

bytes[][] memory outputs = new bytes[][](intent.operations.length);
for (uint256 i = 0; i < intent.operations.length; i++) {
uint8 opType = intent.operations[i].opType;
if (opType == uint8(OpType.Swap) || opType == uint8(OpType.CrossChainSwap)) {
_executeSwap(intent, proposal, i);
} else if (opType == uint8(OpType.Transfer)) _executeTransfer(intent, proposal, i);
else if (opType == uint8(OpType.Call)) _executeCall(intent, proposal, i);
else revert SettlerUnknownOperationType(uint8(opType));
outputs[i] = _executeOperation(intent, proposal, i, outputs);
}

_payFees(intent, proposal);
emit ProposalExecuted(proposal.hash(intent, _msgSender()));
}

/**
* @dev Executes proposal to fulfill an operation
* @param intent Intent being fulfilled
* @param proposal Proposal being executed
* @param index Position where the operation and its corresponding proposal data are located
* @param outputs List of operations outputs
*/
function _executeOperation(Intent memory intent, Proposal memory proposal, uint256 index, bytes[][] memory outputs)
internal
returns (bytes[] memory)
{
uint8 opType = intent.operations[index].opType;
if (opType == uint8(OpType.Swap) || opType == uint8(OpType.CrossChainSwap)) {
return _executeSwap(intent, proposal, index);
}
if (opType == uint8(OpType.Transfer)) return _executeTransfer(intent, proposal, index);
if (opType == uint8(OpType.Call)) return _executeCall(intent, proposal, index);
if (opType == uint8(OpType.DynamicCall)) return _executeDynamicCall(intent, proposal, index, outputs);
revert SettlerUnknownOperationType(opType);
}

/**
* @dev Validates and executes a proposal to fulfill a swap operation
* @param intent Intent that contains swap operation to be fulfilled
* @param proposal Proposal with swap data to be executed
* @param index Position where the swap proposal data and operation are located
*/
function _executeSwap(Intent memory intent, Proposal memory proposal, uint256 index) internal {
function _executeSwap(Intent memory intent, Proposal memory proposal, uint256 index)
internal
returns (bytes[] memory outputs)
{
Operation memory operation = intent.operations[index];
SwapOperation memory swapOperation = abi.decode(operation.data, (SwapOperation));
SwapProposal memory swapProposal = abi.decode(proposal.datas[index], (SwapProposal));
Expand All @@ -237,22 +272,24 @@ contract Settler is ISettler, Ownable, ReentrancyGuard, EIP712 {
uint256[] memory preBalancesOut = _getTokensOutBalance(swapOperation);
IExecutor(swapProposal.executor).execute(intent, proposal, index);

outputs = new bytes[](swapOperation.tokensOut.length);
if (swapOperation.destinationChain == block.chainid) {
uint256[] memory outputs = new uint256[](swapOperation.tokensOut.length);
uint256[] memory amounts = new uint256[](swapOperation.tokensOut.length);
for (uint256 i = 0; i < swapOperation.tokensOut.length; i++) {
TokenOut memory tokenOut = swapOperation.tokensOut[i];
uint256 postBalanceOut = ERC20Helpers.balanceOf(tokenOut.token, address(this));
uint256 preBalanceOut = preBalancesOut[i];
if (postBalanceOut < preBalanceOut) revert SettlerPostBalanceOutLtPre(i, postBalanceOut, preBalanceOut);

outputs[i] = postBalanceOut - preBalanceOut;
amounts[i] = postBalanceOut - preBalanceOut;
uint256 proposedAmount = swapProposal.amountsOut[i];
if (outputs[i] < proposedAmount) revert SettlerAmountOutLtProposed(i, outputs[i], proposedAmount);
if (amounts[i] < proposedAmount) revert SettlerAmountOutLtProposed(i, amounts[i], proposedAmount);

ERC20Helpers.transfer(tokenOut.token, tokenOut.recipient, outputs[i]);
ERC20Helpers.transfer(tokenOut.token, tokenOut.recipient, amounts[i]);
outputs[i] = abi.encode(amounts[i]);
}

_emitOperationEvents(operation, proposal, intent.hash(), index, abi.encode(outputs));
_emitOperationEvents(operation, proposal, intent.hash(), index, abi.encode(amounts));
}
}

Expand All @@ -262,7 +299,10 @@ contract Settler is ISettler, Ownable, ReentrancyGuard, EIP712 {
* @param proposal Transfer proposal to be executed
* @param index Position where the trasnfer proposal data and operation are located
*/
function _executeTransfer(Intent memory intent, Proposal memory proposal, uint256 index) internal {
function _executeTransfer(Intent memory intent, Proposal memory proposal, uint256 index)
internal
returns (bytes[] memory outputs)
{
Operation memory operation = intent.operations[index];
TransferOperation memory transferOperation = abi.decode(operation.data, (TransferOperation));
_validateTransferOperation(transferOperation, proposal.datas[index]);
Expand All @@ -273,6 +313,7 @@ contract Settler is ISettler, Ownable, ReentrancyGuard, EIP712 {
_transferFrom(transfer.token, operation.user, transfer.recipient, transfer.amount, isSmartAccount);
}

outputs = new bytes[](0);
_emitOperationEvents(operation, proposal, intent.hash(), index, new bytes(0));
}

Expand All @@ -282,12 +323,15 @@ contract Settler is ISettler, Ownable, ReentrancyGuard, EIP712 {
* @param proposal Call proposal to be executed
* @param index Position where the call proposal data and operation are located
*/
function _executeCall(Intent memory intent, Proposal memory proposal, uint256 index) internal {
function _executeCall(Intent memory intent, Proposal memory proposal, uint256 index)
internal
returns (bytes[] memory outputs)
{
Operation memory operation = intent.operations[index];
CallOperation memory callOperation = abi.decode(operation.data, (CallOperation));
_validateCallOperation(callOperation, proposal.datas[index], operation.user);

bytes[] memory outputs = new bytes[](callOperation.calls.length);
outputs = new bytes[](callOperation.calls.length);
for (uint256 i = 0; i < callOperation.calls.length; i++) {
CallData memory call = callOperation.calls[i];
// solhint-disable-next-line avoid-low-level-calls
Expand All @@ -297,6 +341,34 @@ contract Settler is ISettler, Ownable, ReentrancyGuard, EIP712 {
_emitOperationEvents(operation, proposal, intent.hash(), index, abi.encode(outputs));
}

/**
* @dev Validates and executes a proposal to fulfill a dynamic call operation
* @param intent Intent that contains dynamic call operation to be fulfilled
* @param proposal Dynamic call proposal to be executed
* @param index Position where the dynamic call proposal data and operation are located
* @param variables List of operations outputs
*/
function _executeDynamicCall(
Intent memory intent,
Proposal memory proposal,
uint256 index,
bytes[][] memory variables
) internal returns (bytes[] memory outputs) {
Operation memory operation = intent.operations[index];
DynamicCallOperation memory dynamicCallOperation = abi.decode(operation.data, (DynamicCallOperation));
_validateDynamicCallOperation(dynamicCallOperation, proposal.datas[index], operation.user);

outputs = new bytes[](dynamicCallOperation.calls.length);
for (uint256 i = 0; i < dynamicCallOperation.calls.length; i++) {
DynamicCall memory dynamicCall = abi.decode(dynamicCallOperation.calls[i], (DynamicCall));
bytes memory data = IDynamicCallEncoder(dynamicCallEncoder).encode(dynamicCall, variables, index);
// solhint-disable-next-line avoid-low-level-calls
outputs[i] = smartAccountsHandler.call(operation.user, dynamicCall.target, data, dynamicCall.value);
}

_emitOperationEvents(operation, proposal, intent.hash(), index, abi.encode(outputs));
}

/**
* @dev Validates an intent and its corresponding proposal
The off-chain validators are assuring that:
Expand Down Expand Up @@ -427,6 +499,22 @@ contract Settler is ISettler, Ownable, ReentrancyGuard, EIP712 {
if (!smartAccountsHandler.isSmartAccount(user)) revert SettlerUserNotSmartAccount(user);
}

/**
* @dev Validates a dynamic call operation and its corresponding proposal
* @param operation Dynamic call operation to be fulfilled
* @param proposalData data of the proposal
* @param user The originator of the operation
*/
function _validateDynamicCallOperation(
DynamicCallOperation memory operation,
bytes memory proposalData,
address user
) internal view {
if (operation.chainId != block.chainid) revert SettlerInvalidChain(block.chainid);
if (proposalData.length > 0) revert SettlerProposalDataNotEmpty();
if (!smartAccountsHandler.isSmartAccount(user)) revert SettlerUserNotSmartAccount(user);
}

/**
* @dev Validates a cross-chain swap operation and its corresponding proposal
* @param operation Swap operation to be fulfilled
Expand Down Expand Up @@ -556,6 +644,16 @@ contract Settler is ISettler, Ownable, ReentrancyGuard, EIP712 {
emit OperationsValidatorSet(newOperationsValidator);
}

/**
* @dev Sets the dynamic call encoder
* @param newDynamicCallEncoder New dynamic call encoder to be set
*/
function _setDynamicCallEncoder(address newDynamicCallEncoder) internal {
if (newDynamicCallEncoder == address(0)) revert SettlerDynamicCallEncoderZero();
dynamicCallEncoder = newDynamicCallEncoder;
emit DynamicCallEncoderSet(newDynamicCallEncoder);
}

/**
* @dev Sets a safeguard for a user
* @param user Address of the user to set the safeguard for
Expand Down
Loading
Loading