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)
}
}
}
Created
December 18, 2019 14:33
-
-
Save andreafspeziale/9cfc7dddc392d75a1863b13c33016ea1 to your computer and use it in GitHub Desktop.
The atomic proxy SC
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment