// SPDX-License-Identifier: agpl-3.0 pragma solidity ^0.6.12; pragma experimental ABIEncoderV2; import "./libs/SafeMath.sol"; import "./libs/SafeERC20.sol"; import "./libs/SignatureValidator.sol"; import "./libs/ChannelLibrary.sol"; import "./AdExCore.sol"; contract Identity { using SafeMath for uint; // Storage // WARNING: be careful when modifying this // privileges and routineAuthorizations must always be 0th and 1th thing in storage, // because of the proxies we generate that delegatecall into this contract (which assume storage slot 0 and 1) mapping (address => uint8) public privileges; // Routine authorizations mapping (bytes32 => bool) public routineAuthorizations; // The next allowed nonce uint public nonce = 0; // Routine operations are authorized at once for a period, fee is paid once mapping (bytes32 => uint256) public routinePaidFees; // Constants bytes4 private constant CHANNEL_WITHDRAW_SELECTOR = bytes4(keccak256('channelWithdraw((address,address,uint256,uint256,address[],bytes32),bytes32,bytes32[3][],bytes32[],uint256)')); bytes4 private constant CHANNEL_WITHDRAW_EXPIRED_SELECTOR = bytes4(keccak256('channelWithdrawExpired((address,address,uint256,uint256,address[],bytes32))')); enum PrivilegeLevel { None, Routines, Transactions } enum RoutineOp { ChannelWithdraw, ChannelWithdrawExpired } // Events event LogPrivilegeChanged(address indexed addr, uint8 privLevel); event LogRoutineAuth(bytes32 hash, bool authorized); // Transaction structure // Those can be executed by keys with >= PrivilegeLevel.Transactions // Even though the contract cannot receive ETH, we are able to send ETH (.value), cause ETH might've been sent to the contract address before it's deployed struct Transaction { // replay protection address identityContract; uint nonce; // tx fee, in tokens address feeTokenAddr; uint feeAmount; // all the regular txn data address to; uint value; bytes data; } // RoutineAuthorizations allow the user to authorize (via keys >= PrivilegeLevel.Routines) a relayer to do any number of routines // those routines are safe: e.g. sweeping channels (withdrawing off-chain balances to the identity) // while the fee will be paid only ONCE per auth per period (1 week), the authorization can be used until validUntil // while the routines are safe, there is some level of implied trust as the relayer may run executeRoutines without any routines to claim the fee struct RoutineAuthorization { address relayer; address outpace; uint validUntil; address feeTokenAddr; uint weeklyFeeAmount; } struct RoutineOperation { RoutineOp mode; bytes data; } constructor(address[] memory addrs, uint8[] memory privLevels) public { uint len = privLevels.length; for (uint i=0; i 0) { SafeERC20.approve(channel.tokenAddr, coreAddr, 0); } SafeERC20.approve(channel.tokenAddr, coreAddr, channel.tokenAmount); AdExCore(coreAddr).channelOpen(channel); } function execute(Transaction[] memory txns, bytes32[3][] memory signatures) public { require(txns.length > 0, 'MUST_PASS_TX'); address feeTokenAddr = txns[0].feeTokenAddr; uint feeAmount = 0; uint len = txns.length; for (uint i=0; i= uint8(PrivilegeLevel.Transactions), 'INSUFFICIENT_PRIVILEGE_TRANSACTION'); nonce = nonce.add(1); feeAmount = feeAmount.add(txn.feeAmount); executeCall(txn.to, txn.value, txn.data); // The actual anti-bricking mechanism - do not allow a signer to drop his own priviledges require(privileges[signer] >= uint8(PrivilegeLevel.Transactions), 'PRIVILEGE_NOT_DOWNGRADED'); } if (feeAmount > 0) { SafeERC20.transfer(feeTokenAddr, msg.sender, feeAmount); } } function executeBySender(Transaction[] memory txns) public { require(privileges[msg.sender] >= uint8(PrivilegeLevel.Transactions), 'INSUFFICIENT_PRIVILEGE_SENDER'); uint len = txns.length; for (uint i=0; i= uint8(PrivilegeLevel.Transactions), 'PRIVILEGE_NOT_DOWNGRADED'); } function executeRoutines(RoutineAuthorization memory auth, RoutineOperation[] memory operations) public { require(auth.validUntil >= now, 'AUTHORIZATION_EXPIRED'); bytes32 hash = keccak256(abi.encode(auth)); require(routineAuthorizations[hash], 'NO_AUTHORIZATION'); uint len = operations.length; for (uint i=0; i 0 && (now - routinePaidFees[hash]) >= 7 days) { routinePaidFees[hash] = now; SafeERC20.transfer(auth.feeTokenAddr, auth.relayer, auth.weeklyFeeAmount); } } // we shouldn't use address.call(), cause: https://github.com/ethereum/solidity/issues/2884 // copied from https://github.com/uport-project/uport-identity/blob/develop/contracts/Proxy.sol // there's also // https://github.com/gnosis/MultiSigWallet/commit/e1b25e8632ca28e9e9e09c81bd20bf33fdb405ce // https://github.com/austintgriffith/bouncer-proxy/blob/master/BouncerProxy/BouncerProxy.sol // https://github.com/gnosis/safe-contracts/blob/7e2eeb3328bb2ae85c36bc11ea6afc14baeb663c/contracts/base/Executor.sol function executeCall(address to, uint256 value, bytes memory data) internal { assembly { let result := call(gas(), to, value, add(data, 0x20), mload(data), 0, 0) switch result case 0 { let size := returndatasize() let ptr := mload(0x40) returndatacopy(ptr, 0, size) revert(ptr, size) } default {} } } }