Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Meta Txs: Adding support for meta transactions in aragon apps (Part 1) #526

Draft
wants to merge 19 commits into
base: next
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
8 changes: 5 additions & 3 deletions .solcover.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ const skipFiles = [
'lib',
'test',
'acl/ACLSyntaxSugar.sol',
'common/DepositableStorage.sol', // Used in tests that send ETH
'common/SafeERC20.sol', // solidity-coverage fails on assembly if (https://github.com/sc-forks/solidity-coverage/issues/287)
'common/UnstructuredStorage.sol' // Used in tests that send ETH
'common/DepositableStorage.sol', // Used in tests that send ETH
'common/SafeERC20.sol', // solidity-coverage fails on assembly if (https://github.com/sc-forks/solidity-coverage/issues/287)
'common/UnstructuredStorage.sol', // Used in tests that send ETH
'relayer/Relayer.sol', // solidity-coverage uses test-rpc which does not implement eth_signTypedData
'relayer/RelayedAragonApp.sol' // solidity-coverage uses test-rpc which does not implement eth_signTypedData
]

module.exports = {
Expand Down
7 changes: 4 additions & 3 deletions contracts/apps/AppStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ import "../kernel/IKernel.sol";
contract AppStorage {
using UnstructuredStorage for bytes32;

/* Hardcoded constants to save gas
bytes32 internal constant KERNEL_POSITION = keccak256("aragonOS.appStorage.kernel");
bytes32 internal constant APP_ID_POSITION = keccak256("aragonOS.appStorage.appId");
/*
* Hardcoded constants to save gas
* bytes32 internal constant KERNEL_POSITION = keccak256("aragonOS.appStorage.kernel");
* bytes32 internal constant APP_ID_POSITION = keccak256("aragonOS.appStorage.appId");
*/
bytes32 internal constant KERNEL_POSITION = 0x4172f0f7d2289153072b0a6ca36959e0cbe2efc3afe50fc81636caa96338137b;
bytes32 internal constant APP_ID_POSITION = 0xd625496217aa6a3453eecb9c3489dc5a53e6c67b444329ea2b2cbc9ff547639b;
Expand Down
8 changes: 6 additions & 2 deletions contracts/apps/AragonApp.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ contract AragonApp is AppStorage, Autopetrified, VaultRecoverable, ReentrancyGua
string private constant ERROR_AUTH_FAILED = "APP_AUTH_FAILED";

modifier auth(bytes32 _role) {
require(canPerform(msg.sender, _role, new uint256[](0)), ERROR_AUTH_FAILED);
require(canPerform(sender(), _role, new uint256[](0)), ERROR_AUTH_FAILED);
_;
}

modifier authP(bytes32 _role, uint256[] _params) {
require(canPerform(msg.sender, _role, _params), ERROR_AUTH_FAILED);
require(canPerform(sender(), _role, _params), ERROR_AUTH_FAILED);
_;
}

Expand Down Expand Up @@ -65,4 +65,8 @@ contract AragonApp is AppStorage, Autopetrified, VaultRecoverable, ReentrancyGua
// Funds recovery via a vault is only available when used with a kernel
return kernel().getRecoveryVault(); // if kernel is not set, it will revert
}

function sender() internal view returns (address) {
return msg.sender;
}
}
53 changes: 53 additions & 0 deletions contracts/common/MemoryHelpers.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
pragma solidity ^0.4.24;


library MemoryHelpers {

function append(bytes memory self, address addr) internal pure returns (bytes memory) {
// alloc required encoded data size
uint256 dataSize = self.length;
uint256 appendedDataSize = dataSize + 32;
bytes memory appendedData = new bytes(appendedDataSize);

// copy data
uint256 inputPointer;
uint256 outputPointer;
assembly {
inputPointer := add(self, 0x20)
outputPointer := add(appendedData, 0x20)
}
memcpy(outputPointer, inputPointer, dataSize);

// append address
assembly {
let signerPointer := add(add(appendedData, 0x20), dataSize)
mstore(signerPointer, addr)
}

return appendedData;
}

// From https://github.com/Arachnid/solidity-stringutils/blob/master/src/strings.sol
function memcpy(uint256 output, uint256 input, uint256 length) internal pure {
uint256 len = length;
uint256 dest = output;
uint256 src = input;

// Copy word-length chunks while possible
for (; len >= 32; len -= 32) {
assembly {
mstore(dest, mload(src))
}
dest += 32;
src += 32;
}

// Copy remaining bytes
uint256 mask = 256 ** (32 - len) - 1;
assembly {
let srcpart := and(mload(src), not(mask))
let destpart := and(mload(dest), mask)
mstore(dest, or(destpart, srcpart))
}
}
}
2 changes: 2 additions & 0 deletions contracts/kernel/IKernel.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
pragma solidity ^0.4.24;

import "../acl/IACL.sol";
import "../relayer/IRelayer.sol";
import "../common/IVaultRecoverable.sol";


Expand All @@ -16,6 +17,7 @@ interface IKernelEvents {
// This should be an interface, but interfaces can't inherit yet :(
contract IKernel is IKernelEvents, IVaultRecoverable {
function acl() public view returns (IACL);
function relayer() public view returns (IRelayer);
function hasPermission(address who, address where, bytes32 what, bytes how) public view returns (bool);

function setApp(bytes32 namespace, bytes32 appId, address app) public;
Expand Down
10 changes: 10 additions & 0 deletions contracts/kernel/Kernel.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import "./KernelConstants.sol";
import "./KernelStorage.sol";
import "../acl/IACL.sol";
import "../acl/ACLSyntaxSugar.sol";
import "../relayer/IRelayer.sol";
import "../common/ConversionHelpers.sol";
import "../common/IsContract.sol";
import "../common/Petrifiable.sol";
Expand Down Expand Up @@ -169,6 +170,7 @@ contract Kernel is IKernel, KernelStorage, KernelAppIds, KernelNamespaceConstant
function APP_ADDR_NAMESPACE() external pure returns (bytes32) { return KERNEL_APP_ADDR_NAMESPACE; }
function KERNEL_APP_ID() external pure returns (bytes32) { return KERNEL_CORE_APP_ID; }
function DEFAULT_ACL_APP_ID() external pure returns (bytes32) { return KERNEL_DEFAULT_ACL_APP_ID; }
function DEFAULT_RELAYER_APP_ID() external pure returns (bytes32) { return KERNEL_DEFAULT_RELAYER_APP_ID; }
/* solium-enable function-order, mixedcase */

/**
Expand Down Expand Up @@ -197,6 +199,14 @@ contract Kernel is IKernel, KernelStorage, KernelAppIds, KernelNamespaceConstant
return IACL(getApp(KERNEL_APP_ADDR_NAMESPACE, KERNEL_DEFAULT_ACL_APP_ID));
}

/**
* @dev Get the installed Relayer app
* @return Relayer app
*/
function relayer() public view returns (IRelayer) {
return IRelayer(getApp(KERNEL_APP_ADDR_NAMESPACE, KERNEL_DEFAULT_RELAYER_APP_ID));
}

/**
* @dev Function called by apps to check ACL on kernel or to check permission status
* @param _who Sender of the original call
Expand Down
2 changes: 2 additions & 0 deletions contracts/kernel/KernelConstants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ contract KernelAppIds {
bytes32 internal constant KERNEL_CORE_APP_ID = apmNamehash("kernel");
bytes32 internal constant KERNEL_DEFAULT_ACL_APP_ID = apmNamehash("acl");
bytes32 internal constant KERNEL_DEFAULT_VAULT_APP_ID = apmNamehash("vault");
bytes32 internal constant KERNEL_DEFAULT_VAULT_APP_ID = apmNamehash("relayer");
*/
bytes32 internal constant KERNEL_CORE_APP_ID = 0x3b4bf6bf3ad5000ecf0f989d5befde585c6860fea3e574a4fab4c49d1c177d9c;
bytes32 internal constant KERNEL_DEFAULT_ACL_APP_ID = 0xe3262375f45a6e2026b7e7b18c2b807434f2508fe1a2a3dfb493c7df8f4aad6a;
bytes32 internal constant KERNEL_DEFAULT_VAULT_APP_ID = 0x7e852e0fcfce6551c13800f1e7476f982525c2b5277ba14b24339c68416336d1;
bytes32 internal constant KERNEL_DEFAULT_RELAYER_APP_ID = 0x7641595d1a2007abf0fe95c31d0b7a822954acbf6fb0cbe3bd1161d9dec9e1d3;
}


Expand Down
33 changes: 33 additions & 0 deletions contracts/lib/misc/EIP712.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
pragma solidity ^0.4.24;


contract EIP712 {
string private constant DOMAIN_TYPE = "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)";
bytes32 private constant DOMAIN_TYPEHASH = keccak256(DOMAIN_TYPE);

struct Domain {
string name;
string version;
uint256 chainId;
address verifyingContract;
}

function _domainSeparator(string name, string version, uint256 chainId, address verifyingContract) internal pure returns (bytes32) {
return _hash(Domain({
name: name,
version: version,
chainId: chainId,
verifyingContract: verifyingContract
}));
}

function _hash(Domain domain) internal pure returns (bytes32) {
return keccak256(abi.encode(
DOMAIN_TYPEHASH,
keccak256(bytes(domain.name)),
keccak256(bytes(domain.version)),
domain.chainId,
domain.verifyingContract
));
}
}
69 changes: 69 additions & 0 deletions contracts/lib/sig/ECDSA.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
pragma solidity ^0.4.24;


/**
* @title Elliptic curve signature operations
* @dev Based on https://github.com/OpenZeppelin/openzeppelin-solidity/blob/v2.0.0/contracts/cryptography/ECDSA.sol
*/
library ECDSA {

/**
* @dev Recover signer address from a message by using their signature
* @param hash bytes32 message, the hash is the signed message. What is recovered is the signer address.
* @param signature bytes signature, the signature is generated using web3.eth.sign()
*/
function recover(bytes32 hash, bytes signature)
internal
pure
returns (address)
{
bytes32 r;
bytes32 s;
uint8 v;

// Check the signature length
if (signature.length != 65) {
return (address(0));
}

// 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, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}

// 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
if (v != 27 && v != 28) {
return (address(0));
} else {
// solium-disable-next-line arg-overflow
return ecrecover(hash, v, r, s);
}
}

/**
* toEthSignedMessageHash
* @dev prefix a bytes32 value with "\x19Ethereum Signed Message:"
* and hash the result
*/
function toEthSignedMessageHash(bytes32 hash)
internal
pure
returns (bytes32)
{
// 32 is the length in bytes of hash,
// enforced by the type signature above
return keccak256(
abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)
);
}
}
6 changes: 6 additions & 0 deletions contracts/relayer/IRelayer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pragma solidity ^0.4.24;


contract IRelayer {
function relay(address from, address to, uint256 nonce, bytes data, uint256 gasRefund, uint256 gasPrice, bytes signature) external;
}
34 changes: 34 additions & 0 deletions contracts/relayer/RelayedAragonApp.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
pragma solidity ^0.4.24;

import "./IRelayer.sol";
import "../apps/AragonApp.sol";


contract RelayedAragonApp is AragonApp {

function sender() internal view returns (address) {
address relayer = address(_relayer());
if (msg.sender != relayer) {
return msg.sender;
}

address signer = _decodeSigner();
return signer != address(0) ? signer : relayer;
}

function _decodeSigner() internal pure returns (address signer) {
// Note that calldatasize includes one word more than the original calldata array, due to the address of the
// signer that is being appended at the end of it. Thus, we are loading the last word of the calldata array to
// fetch the actual signed of the relayed call
assembly {
let ptr := mload(0x40)
mstore(0x40, add(ptr, 0x20))
calldatacopy(ptr, sub(calldatasize, 0x20), 0x20)
signer := mload(ptr)
}
}

function _relayer() internal view returns (IRelayer) {
return kernel().relayer();
}
}
Loading