Skip to content

Instantly share code, notes, and snippets.

@andreafspeziale
Created December 18, 2019 14:33
Show Gist options
  • Save andreafspeziale/9cfc7dddc392d75a1863b13c33016ea1 to your computer and use it in GitHub Desktop.
Save andreafspeziale/9cfc7dddc392d75a1863b13c33016ea1 to your computer and use it in GitHub Desktop.
The atomic proxy SC
pragma solidity ^0.5.10;

import "./Utils/Withdrawable.sol";

import "openzeppelin-solidity/contracts/lifecycle/Pausable.sol";
import "solidity-bytes-utils/contracts/BytesLib.sol";

/**
 * @title Proxy.
 * @dev Proxy meta transactions.
 */
contract Proxy is Pausable, Withdrawable {
  using BytesLib for bytes;

  mapping (bytes32 => bool) isOperationAlreadyProcessed;

  /**
   * @dev Ensure subset is contained in set.
   * @param set    The set of elements.
   * @param subset The eventually subset of elements.
   */
  function isSubset(
    address[] memory set,
    address[] memory subset
  )
    public
    pure
    returns (bool)
  {
    for (uint i = 0; i < subset.length; i++) {
      bool found = false;
      for (uint j = 0; j < set.length; j++) {
        if (subset[i] == set[j]) {
          found = true;
          continue;
        }
      }
      if (found == false) {
        return false;
      }
    }
    return true;
  }

  /**
   * @dev Return the first param of the meta tx data field which must be an address.
   * E.g: in transferFrom(sender, to, amount) sender address is returned.
   * @param dataField The meta transactions data field.
   */
  function getAddressFromData(bytes memory dataField)
    public
    pure
    returns (address)
  {
    address sender;
    // solium-disable-next-line security/no-inline-assembly
    assembly {
        sender := mload(add(dataField, 36))
    }
    return sender;
  }

  /**
   * @dev Returns the signer of a signed message.
   * @param hash      The hash of the entire package which contains one or more meta tx.
   * @param signature The signed package which contains one or more meta tx.
   */
  function getAddressFromSignature(
    bytes32 hash,
    bytes memory signature
  )
    public
    pure
    returns (address)
  {
    bytes32 r;
    bytes32 s;
    uint8 v;
    // Check the signature length
    require(signature.length == 65, "fn: getAddressFromSignature(), msg: wrong signature length");
    // Divide the signature in r, s and v variables
    // ecrecover takes the signature parameters, and the only way to get them
    // currently is to use assembly.
    // solium-disable-next-line security/no-inline-assembly
    assembly {
      r := mload(add(signature, 32))
      s := mload(add(signature, 64))
      v := byte(0, mload(add(signature, 96)))
    }
    // Version of signature should be 27 or 28, but 0 and 1 are also possible versions
    if (v < 27) {
      v += 27;
    }
    // If the version is correct return the signer address
    require(v == 27 || v == 28, "fn: getAddressFromSignature(), msg: wrong signature v value");
    return ecrecover(
      keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)),
      v, r, s
    );
  }

  /**
   * @dev Returns the entire package hash which contains one or more meta tx.
   * @param recipients          The meta transactions recipients.
   * @param txsValueField       The meta transactions value fields.
   * @param packedTxsDataField  The concatenated meta transactions data fields.
   * @param salt                The entire package salt.
   * @param expiration          The operation expiration timestamp.
   */
  function getHash(
    address[] memory recipients,
    uint[] memory txsValueField,
    bytes memory packedTxsDataField,
    uint salt,
    uint expiration
  )
    public
    pure
    returns (bytes32)
  {
    return keccak256(
      abi.encodePacked(
        recipients,
        txsValueField,
        packedTxsDataField,
        salt,
        expiration
      )
    );
  }

  /**
   * @dev Returns the sum of the uint array elements.
   * @param amounts The array of uint elements.
   */
  function getSum(uint[] memory amounts) public pure returns(uint) {
    uint sum = 0;
    for (uint i = 0; i < amounts.length; i++) {
      sum += amounts[i];
    }
    return sum;
  }

  /**
   * @dev Unpack and forward a package of meta transactions based on provided signatures.
   * @param packedSignature    The concatenated meta transactions signed hashed messages.
   * @param recipients         The meta transactions recipients.
   * @param txsValueField      The meta transactions value fields.
   * @param packedTxsDataField The concatenated meta transactions data fields.
   * @param txsDataSizes       The size of each meta transactions data field.
   * @param salt               The meta transactions salt.
   * @param expiration         The operation expiration timestamp.
   */
  function forward(
    bytes memory packedSignature,
    address[] memory recipients,
    uint[] memory txsValueField,
    bytes memory packedTxsDataField,
    uint32[] memory txsDataSizes,
    uint salt, // managed outside
    uint expiration
  )
    public
    payable
    whenNotPaused
    returns (bool)
  {
    require(expiration > block.number, "fn: forward(), msg: operation expired");
    require(
      getSum(txsValueField) == msg.value,
      "fn: forward(), msg: missmatch txsValueField and msg.value"
    );
    bytes[] memory txsDataField = unpack(packedTxsDataField, txsDataSizes);
    require(
      recipients.length == txsValueField.length && recipients.length == txsDataField.length,
      "fn: forward(), msg: missmatch between the recipients, txsValueField, txsDataField length"
    );
    bytes[] memory signatures = unpack(packedSignature, 65);
    bytes32 hash = getHash(
      recipients,
      txsValueField,
      packedTxsDataField,
      salt,
      expiration
    );

    require(!isOperationAlreadyProcessed[hash], "fn: forward(), msg: operation already processed");
    isOperationAlreadyProcessed[hash] = true;

    address[] memory senders = new address[](txsDataField.length);
    for (uint i = 0; i < txsDataField.length; i++) {
      senders[i] = getAddressFromData(txsDataField[i]);
    }

    address[] memory signers = new address[](signatures.length + 1);
    for (uint j = 0; j < signatures.length; j++) {
      signers[j] = getAddressFromSignature(hash, signatures[j]);
    }
    // save the msg.sender signature from the packedSignature
    signers[signers.length - 1] = msg.sender;
    require(isSubset(signers, senders), "fn: forward(), msg: signer missing");

    for (uint x = 0; x < signatures.length; x++) {
      require(
        executeCall(
          recipients[x],
          txsValueField[x],
          txsDataField[x]),
        "fn: forward(), msg: executeCall() function failed"
      );
    }
    return true;
  }

  /**
   * @dev Unpack and forward a package of meta transactions based on provided signatures.
   * The value field is omitted to reduce costs when msg.value is not used.
   * @param packedSignature    The concatenated meta transactions signed hashed messages.
   * @param recipients         The meta transactions recipients.
   * @param packedTxsDataField The concatenated meta transactions data fields.
   * @param txsDataSizes       The size of each meta transactions data field.
   * @param salt               The meta transactions salt.
   * @param expiration         The operation expiration timestamp.
   */
  function forwardWithNoValue(
    bytes memory packedSignature,
    address[] memory recipients,
    bytes memory packedTxsDataField,
    uint32[] memory txsDataSizes,
    uint salt, // managed outside
    uint expiration
  )
    public
    whenNotPaused
    returns (bool)
  {
    require(expiration > block.timestamp, "fn: forward(), msg: operation expired");
    bytes[] memory txsDataField = unpack(packedTxsDataField, txsDataSizes);
    require(
      recipients.length == txsDataField.length,
      "fn: forward(), msg: missmatch between the recipients and txsDataField length"
    );
    bytes[] memory signatures = unpack(packedSignature, 65);
    uint[] memory txsValueField = new uint[](txsDataField.length);
    for (uint x = 0; x < txsDataField.length; x++) {
      txsValueField[x] = 0;
    }
    bytes32 hash = getHash(
      recipients,
      txsValueField,
      packedTxsDataField,
      salt,
      expiration
    );
    address[] memory senders = new address[](txsDataField.length);
    for (uint i = 0; i < txsDataField.length; i++) {
      senders[i] = getAddressFromData(txsDataField[i]);
    }
    address[] memory signers = new address[](signatures.length + 1);
    for (uint j = 0; j < signatures.length; j++) {
      signers[j] = getAddressFromSignature(hash, signatures[j]);
    }
    // save the msg.sender signature from the packedSignature
    signers[signers.length - 1] = msg.sender;
    require(isSubset(signers, senders), "fn: forward(), msg: signer missing");
    for (uint z = 0; z < txsDataField.length; z++) {
      require(
        executeCall(
          recipients[z],
          0,
          txsDataField[z]),
        "fn: forward(), msg: executeCall() function failed"
      );
    }
    return true;
  }

  /**
   * @dev Destroy the contract.
   * Only the owner can destroy the contract.
   */
  function kill() public onlyOwner {
    selfdestruct(msg.sender);
  }

  /**
   * @dev Returns an array of bytes from the concat of bytes of a fixed size.
   * @param data The concatenated bytes.
   * @param size The bytes size.
   */
  function unpack(
    bytes memory data,
    uint size
  )
    internal
    pure
    returns(bytes[] memory)
  {
    bytes[] memory results = new bytes[](data.length/size);
    require(
      results.length * size == data.length,
      "fn: unpack(), msg: wrong data length (fixed)"
    );
    for (uint i = 0; i < results.length; i++) {
      results[i] = data.slice(size * i, size);
    }
    return results;
  }

  /**
   * @dev Returns an array of bytes from the concat of bytes of a dynamic size.
   * @param data  The concatenated bytes.
   * @param sizes The size of each bytes.
   */
  function unpack(
    bytes memory data,
    uint32[] memory sizes
  )
    internal
    pure
    returns(bytes[] memory)
  {
    bytes[] memory results = new bytes[](sizes.length);
    uint position = 0;
    for (uint i = 0; i < sizes.length; i++) {
      results[i] = data.slice(position, sizes[i]);
      position += sizes[i];
    }
    require(
      position == data.length,
      "fn: unpack(), msg: wrong data length (dynamic)"
    );
    return results;
  }

  /**
   * @dev Forward meta transactions based on provided signatures.
   * @param to    The meta transaction recipients.
   * @param value The meta transaction value field.
   * @param data  The meta transaction data field.
   */
  function executeCall(
    address to,
    uint256 value,
    bytes memory data
  )
    private
    returns (bool success)
  {
    // solium-disable-next-line security/no-inline-assembly
    assembly {
       success := call(gas, to, value, add(data, 0x20), mload(data), 0, 0)
    }
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment