ETH Price: $2,575.19 (+0.88%)

Transaction Decoder

Block:
11443985 at Dec-13-2020 10:02:49 AM +UTC
Transaction Fee:
0.0030663329 ETH $7.90
Gas Used:
67,097 Gas / 45.7 Gwei

Account State Difference:

  Address   Before After State Difference Code
0xC2683080...a920206BE
10.73986061499589819 Eth
Nonce: 11157
10.73679428209589819 Eth
Nonce: 11158
0.0030663329
8.334764935014370559 Eth8.337831267914370559 Eth0.0030663329

Execution Trace

UpgradeBeaconProxyV1.d07490f6( )
  • DharmaUpgradeBeacon.STATICCALL( )
  • DharmaSmartWalletImplementationV14.executeActionWithAtomicBatchCalls( calls=, minimumActionGas=0, userSignature=0xBFDD47BF88F2080AD076DF71666AB5A592AA5B47574EC5ED6094246A039953E47FB2EFFA1D6C293B27F9C638984016B3E13D3E666FD3F3EA467EBD481FD4A1C81B, dharmaSignature=0x879C4549F67B051D432F184968583CE6FD265820A660D4C1F67E29B3CAA156F84E73E150AAFE994C24179DB5B906056330651D7D92AA307F82B688543A6D6D6D1C )
    • DharmaKeyRegistryV2.STATICCALL( )
    • 0x71adb9db79a046a671e245f0cfb751fc420fdf8b.20c13b0b( )
      • DharmaKeyRingUpgradeBeacon.STATICCALL( )
      • DharmaKeyRingImplementationV1.isValidSignature( data=0xB3F202D1B0761D4DFA31AC8A1EEE9B052FF346C8EB5EFEA251925EC8DA5D2D340000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002C00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000B62132E35A6C13EE1EE0F84DC5D40BAD8D815206000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095EA7B30000000000000000000000000EFB068354C10C070DDD64A0E8EAF8F054DF7E2600000000000000000000000000000000000000000000001043561A8829300000000000000000000000000000000000000000000000000000000000000000000000000000000000000EFB068354C10C070DDD64A0E8EAF8F054DF7E260000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000C4133D214C000000000000000000000000B62132E35A6C13EE1EE0F84DC5D40BAD8D815206000000000000000000000000A0B86991C6218B36C1D19D4A2E9EB0CE3606EB4800000000000000000000000000000000000000000000001043561A8829300000000000000000000000000000000000000000000000000000000000000949DA23000000000000000000000000000000000000000000000000000000005FD5EB01000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000, signature=0xBFDD47BF88F2080AD076DF71666AB5A592AA5B47574EC5ED6094246A039953E47FB2EFFA1D6C293B27F9C638984016B3E13D3E666FD3F3EA467EBD481FD4A1C81B ) => ( magicValue=System.Byte[] )
        • Null: 0x000...001.b3f202d1( )
          File 1 of 6: UpgradeBeaconProxyV1
          pragma solidity 0.5.11; // optimization runs: 200, evm version: petersburg
          
          
          /**
           * @title UpgradeBeaconProxyV1
           * @author 0age
           * @notice This contract delegates all logic, including initialization, to an
           * implementation contract specified by a hard-coded "upgrade beacon" contract.
           * Note that this implementation can be reduced in size by stripping out the
           * metadata hash, or even more significantly by using a minimal upgrade beacon
           * proxy implemented using raw EVM opcodes.
           */
          contract UpgradeBeaconProxyV1 {
            // Set upgrade beacon address as a constant (i.e. not in contract storage).
            address private constant _UPGRADE_BEACON = address(
              0x000000000026750c571ce882B17016557279ADaa
            );
          
            /**
             * @notice In the constructor, perform initialization via delegatecall to the
             * implementation set on the upgrade beacon, supplying initialization calldata
             * as a constructor argument. The deployment will revert and pass along the
             * revert reason in the event that this initialization delegatecall reverts.
             * @param initializationCalldata Calldata to supply when performing the
             * initialization delegatecall.
             */
            constructor(bytes memory initializationCalldata) public payable {
              // Delegatecall into the implementation, supplying initialization calldata.
              (bool ok, ) = _implementation().delegatecall(initializationCalldata);
              
              // Revert and include revert data if delegatecall to implementation reverts.
              if (!ok) {
                assembly {
                  returndatacopy(0, 0, returndatasize)
                  revert(0, returndatasize)
                }
              }
            }
          
            /**
             * @notice In the fallback, delegate execution to the implementation set on
             * the upgrade beacon.
             */
            function () external payable {
              // Delegate execution to implementation contract provided by upgrade beacon.
              _delegate(_implementation());
            }
          
            /**
             * @notice Private view function to get the current implementation from the
             * upgrade beacon. This is accomplished via a staticcall to the beacon with no
             * data, and the beacon will return an abi-encoded implementation address.
             * @return implementation Address of the implementation.
             */
            function _implementation() private view returns (address implementation) {
              // Get the current implementation address from the upgrade beacon.
              (bool ok, bytes memory returnData) = _UPGRADE_BEACON.staticcall("");
              
              // Revert and pass along revert message if call to upgrade beacon reverts.
              require(ok, string(returnData));
          
              // Set the implementation to the address returned from the upgrade beacon.
              implementation = abi.decode(returnData, (address));
            }
          
            /**
             * @notice Private function that delegates execution to an implementation
             * contract. This is a low level function that doesn't return to its internal
             * call site. It will return whatever is returned by the implementation to the
             * external caller, reverting and returning the revert data if implementation
             * reverts.
             * @param implementation Address to delegate.
             */
            function _delegate(address implementation) private {
              assembly {
                // Copy msg.data. We take full control of memory in this inline assembly
                // block because it will not return to Solidity code. We overwrite the
                // Solidity scratch pad at memory position 0.
                calldatacopy(0, 0, calldatasize)
          
                // Delegatecall to the implementation, supplying calldata and gas.
                // Out and outsize are set to zero - instead, use the return buffer.
                let result := delegatecall(gas, implementation, 0, calldatasize, 0, 0)
          
                // Copy the returned data from the return buffer.
                returndatacopy(0, 0, returndatasize)
          
                switch result
                // Delegatecall returns 0 on error.
                case 0 { revert(0, returndatasize) }
                default { return(0, returndatasize) }
              }
            }
          }

          File 2 of 6: DharmaUpgradeBeacon
          pragma solidity 0.5.11; // optimization runs: 200, evm version: petersburg
          
          
          /**
           * @title DharmaUpgradeBeacon
           * @author 0age
           * @notice This contract holds the address of the current implementation for
           * Dharma smart wallets and lets a controller update that address in storage.
           */
          contract DharmaUpgradeBeacon {
            // The implementation address is held in storage slot zero.
            address private _implementation;
          
            // The controller that can update the implementation is set as a constant.
            address private constant _CONTROLLER = address(
              0x00000000002226C940b74d674B85E4bE05539663
            );
          
            /**
             * @notice In the fallback function, allow only the controller to update the
             * implementation address - for all other callers, return the current address.
             * Note that this requires inline assembly, as Solidity fallback functions do
             * not natively take arguments or return values.
             */
            function () external {
              // Return implementation address for all callers other than the controller.
              if (msg.sender != _CONTROLLER) {
                // Load implementation from storage slot zero into memory and return it.
                assembly {
                  mstore(0, sload(0))
                  return(0, 32)
                }
              } else {
                // Set implementation - put first word in calldata in storage slot zero.
                assembly { sstore(0, calldataload(0)) }
              }
            }
          }

          File 3 of 6: DharmaSmartWalletImplementationV14
          pragma solidity 0.5.17; // optimization runs: 200, evm version: istanbul
          // WARNING - `executeActionWithAtomicBatchCalls` has a `bytes[]` argument that
          // requires ABIEncoderV2. Exercise caution when calling that specific function.
          pragma experimental ABIEncoderV2;
          
          
          interface DharmaSmartWalletImplementationV1Interface {
            event CallSuccess(
              bytes32 actionID,
              bool rolledBack,
              uint256 nonce,
              address to,
              uint256 value,
              bytes data,
              bytes returnData
            );
          
            event CallFailure(
              bytes32 actionID,
              uint256 nonce,
              address to,
              uint256 value,
              bytes data,
              string revertReason
            );
          
            // ABIEncoderV2 uses an array of Calls for executing generic batch calls.
            struct Call {
              address to;
              uint96 value;
              bytes data;
            }
          
            // ABIEncoderV2 uses an array of CallReturns for handling generic batch calls.
            struct CallReturn {
              bool ok;
              bytes returnData;
            }
          
            function withdrawEther(
              uint256 amount,
              address payable recipient,
              uint256 minimumActionGas,
              bytes calldata userSignature,
              bytes calldata dharmaSignature
            ) external returns (bool ok);
          
            function executeAction(
              address to,
              bytes calldata data,
              uint256 minimumActionGas,
              bytes calldata userSignature,
              bytes calldata dharmaSignature
            ) external returns (bool ok, bytes memory returnData);
          
            function recover(address newUserSigningKey) external;
          
            function executeActionWithAtomicBatchCalls(
              Call[] calldata calls,
              uint256 minimumActionGas,
              bytes calldata userSignature,
              bytes calldata dharmaSignature
            ) external returns (bool[] memory ok, bytes[] memory returnData);
          
            function getNextGenericActionID(
              address to,
              bytes calldata data,
              uint256 minimumActionGas
            ) external view returns (bytes32 actionID);
          
            function getGenericActionID(
              address to,
              bytes calldata data,
              uint256 nonce,
              uint256 minimumActionGas
            ) external view returns (bytes32 actionID);
          
            function getNextGenericAtomicBatchActionID(
              Call[] calldata calls,
              uint256 minimumActionGas
            ) external view returns (bytes32 actionID);
          
            function getGenericAtomicBatchActionID(
              Call[] calldata calls,
              uint256 nonce,
              uint256 minimumActionGas
            ) external view returns (bytes32 actionID);
          }
          
          
          interface DharmaSmartWalletImplementationV3Interface {
            event Cancel(uint256 cancelledNonce);
            event EthWithdrawal(uint256 amount, address recipient);
          }
          
          
          interface DharmaSmartWalletImplementationV4Interface {
            event Escaped();
          
            function setEscapeHatch(
              address account,
              uint256 minimumActionGas,
              bytes calldata userSignature,
              bytes calldata dharmaSignature
            ) external;
          
            function removeEscapeHatch(
              uint256 minimumActionGas,
              bytes calldata userSignature,
              bytes calldata dharmaSignature
            ) external;
          
            function permanentlyDisableEscapeHatch(
              uint256 minimumActionGas,
              bytes calldata userSignature,
              bytes calldata dharmaSignature
            ) external;
          
            function escape(address token) external;
          
          }
          
          
          interface DharmaSmartWalletImplementationV7Interface {
            // Fires when a new user signing key is set on the smart wallet.
            event NewUserSigningKey(address userSigningKey);
          
            // Fires when an error occurs as part of an attempted action.
            event ExternalError(address indexed source, string revertReason);
          
            // The smart wallet recognizes DAI, USDC, ETH, and SAI as supported assets.
            enum AssetType {
              DAI,
              USDC,
              ETH,
              SAI
            }
          
            // Actions, or protected methods (i.e. not deposits) each have an action type.
            enum ActionType {
              Cancel,
              SetUserSigningKey,
              Generic,
              GenericAtomicBatch,
              SAIWithdrawal,
              USDCWithdrawal,
              ETHWithdrawal,
              SetEscapeHatch,
              RemoveEscapeHatch,
              DisableEscapeHatch,
              DAIWithdrawal,
              SignatureVerification,
              TradeEthForDai,
              DAIBorrow,
              USDCBorrow
            }
          
            function initialize(address userSigningKey) external;
          
            function repayAndDeposit() external;
          
            function withdrawDai(
              uint256 amount,
              address recipient,
              uint256 minimumActionGas,
              bytes calldata userSignature,
              bytes calldata dharmaSignature
            ) external returns (bool ok);
          
            function withdrawUSDC(
              uint256 amount,
              address recipient,
              uint256 minimumActionGas,
              bytes calldata userSignature,
              bytes calldata dharmaSignature
            ) external returns (bool ok);
          
            function cancel(
              uint256 minimumActionGas,
              bytes calldata signature
            ) external;
          
            function setUserSigningKey(
              address userSigningKey,
              uint256 minimumActionGas,
              bytes calldata userSignature,
              bytes calldata dharmaSignature
            ) external;
          
            function migrateSaiToDai() external;
          
            function migrateCSaiToDDai() external;
          
            function migrateCDaiToDDai() external;
          
            function migrateCUSDCToDUSDC() external;
          
            function getBalances() external view returns (
              uint256 daiBalance,
              uint256 usdcBalance,
              uint256 etherBalance,
              uint256 dDaiUnderlyingDaiBalance,
              uint256 dUsdcUnderlyingUsdcBalance,
              uint256 dEtherUnderlyingEtherBalance // always returns zero
            );
          
            function getUserSigningKey() external view returns (address userSigningKey);
          
            function getNonce() external view returns (uint256 nonce);
          
            function getNextCustomActionID(
              ActionType action,
              uint256 amount,
              address recipient,
              uint256 minimumActionGas
            ) external view returns (bytes32 actionID);
          
            function getCustomActionID(
              ActionType action,
              uint256 amount,
              address recipient,
              uint256 nonce,
              uint256 minimumActionGas
            ) external view returns (bytes32 actionID);
          
            function getVersion() external pure returns (uint256 version);
          }
          
          
          interface DharmaSmartWalletImplementationV8Interface {
            function tradeEthForDaiAndMintDDai(
              uint256 ethToSupply,
              uint256 minimumDaiReceived,
              address target,
              bytes calldata data,
              uint256 minimumActionGas,
              bytes calldata userSignature,
              bytes calldata dharmaSignature
            ) external returns (bool ok, bytes memory returnData);
          
            function getNextEthForDaiActionID(
              uint256 ethToSupply,
              uint256 minimumDaiReceived,
              address target,
              bytes calldata data,
              uint256 minimumActionGas
            ) external view returns (bytes32 actionID);
          
            function getEthForDaiActionID(
              uint256 ethToSupply,
              uint256 minimumDaiReceived,
              address target,
              bytes calldata data,
              uint256 nonce,
              uint256 minimumActionGas
            ) external view returns (bytes32 actionID);
          }
          
          
          interface DharmaSmartWalletImplementationV12Interface {
            function setApproval(address token, uint256 amount) external;
          }
          
          interface DharmaSmartWalletImplementationV13Interface {
            function redeemAllDDai() external;
            function redeemAllDUSDC() external;
          }
          
          
          interface ERC20Interface {
            function transfer(address recipient, uint256 amount) external returns (bool);
            function approve(address spender, uint256 amount) external returns (bool);
          
            function balanceOf(address account) external view returns (uint256);
            function allowance(
              address owner, address spender
            ) external view returns (uint256);
          }
          
          
          interface ERC1271Interface {
            function isValidSignature(
              bytes calldata data, bytes calldata signature
            ) external view returns (bytes4 magicValue);
          }
          
          
          interface DTokenInterface {
            // These external functions trigger accrual on the dToken and backing cToken.
            function mint(uint256 underlyingToSupply) external returns (uint256 dTokensMinted);
            function redeem(uint256 dTokensToBurn) external returns (uint256 underlyingReceived);
            function redeemUnderlying(uint256 underlyingToReceive) external returns (uint256 dTokensBurned);
          
            // These external functions only trigger accrual on the dToken.
            function mintViaCToken(uint256 cTokensToSupply) external returns (uint256 dTokensMinted);
          
            // View and pure functions do not trigger accrual on the dToken or the cToken.
            function balanceOfUnderlying(address account) external view returns (uint256 underlyingBalance);
          }
          
          
          interface DharmaKeyRegistryInterface {
            function getKey() external view returns (address key);
          }
          
          
          interface DharmaEscapeHatchRegistryInterface {
            function setEscapeHatch(address newEscapeHatch) external;
          
            function removeEscapeHatch() external;
          
            function permanentlyDisableEscapeHatch() external;
          
            function getEscapeHatch() external view returns (
              bool exists, address escapeHatch
            );
          }
          
          
          interface RevertReasonHelperInterface {
            function reason(uint256 code) external pure returns (string memory);
          }
          
          
          interface EtherizedInterface {
            function triggerEtherTransfer(
              address payable target, uint256 value
            ) external returns (bool success);
          }
          
          
          library Address {
            function isContract(address account) internal view returns (bool) {
              uint256 size;
              assembly { size := extcodesize(account) }
              return size > 0;
            }
          }
          
          
          library ECDSA {
            function recover(
              bytes32 hash, bytes memory signature
            ) internal pure returns (address) {
              if (signature.length != 65) {
                return (address(0));
              }
          
              bytes32 r;
              bytes32 s;
              uint8 v;
          
              assembly {
                r := mload(add(signature, 0x20))
                s := mload(add(signature, 0x40))
                v := byte(0, mload(add(signature, 0x60)))
              }
          
              if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
                return address(0);
              }
          
              if (v != 27 && v != 28) {
                return address(0);
              }
          
              return ecrecover(hash, v, r, s);
            }
          
            function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
              return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
            }
          }
          
          
          contract Etherized is EtherizedInterface {
            address private constant _ETHERIZER = address(
              0x723B51b72Ae89A3d0c2a2760f0458307a1Baa191 
            );
          
            function triggerEtherTransfer(
              address payable target, uint256 amount
            ) external returns (bool success) {
              require(msg.sender == _ETHERIZER, "Etherized: only callable by Etherizer");
              (success, ) = target.call.value(amount)("");
              if (!success) {
                assembly {
                  returndatacopy(0, 0, returndatasize())
                  revert(0, returndatasize())
                }
              }
            }
          }
          
          
          /**
           * @title DharmaSmartWalletImplementationV14
           * @author 0age
           * @notice The V14 implementation for the Dharma smart wallet is a non-custodial,
           * meta-transaction-enabled wallet with helper functions to facilitate lending
           * funds through Dharma Dai and Dharma USD Coin (which in turn use CompoundV2),
           * and with an added security backstop provided by Dharma Labs prior to making
           * withdrawals. It adds support for Dharma Dai and Dharma USD Coin - they employ
           * the respective cTokens as backing tokens and mint and redeem them internally
           * as interest-bearing collateral. This implementation also contains methods to
           * support account recovery, escape hatch functionality, and generic actions,
           * including in an atomic batch. The smart wallet instances utilizing this
           * implementation are deployed through the Dharma Smart Wallet Factory via
           * `CREATE2`, which allows for their address to be known ahead of time, and any
           * Dai or USDC that has already been sent into that address will automatically
           * be deposited into the respective Dharma Token upon deployment of the new
           * smart wallet instance. V14 allows for Ether transfers as part of generic
           * actions, supports "simulation" of generic batch actions, and revises escape
           * hatch functionality to support arbitrary token withdrawals.
           */
          contract DharmaSmartWalletImplementationV14 is
            DharmaSmartWalletImplementationV1Interface,
            DharmaSmartWalletImplementationV3Interface,
            DharmaSmartWalletImplementationV4Interface,
            DharmaSmartWalletImplementationV7Interface,
            DharmaSmartWalletImplementationV8Interface,
            DharmaSmartWalletImplementationV12Interface,
            DharmaSmartWalletImplementationV13Interface,
            ERC1271Interface,
            Etherized {
            using Address for address;
            using ECDSA for bytes32;
            // WARNING: DO NOT REMOVE OR REORDER STORAGE WHEN WRITING NEW IMPLEMENTATIONS!
          
            // The user signing key associated with this account is in storage slot 0.
            // It is the core differentiator when it comes to the account in question.
            address private _userSigningKey;
          
            // The nonce associated with this account is in storage slot 1. Every time a
            // signature is submitted, it must have the appropriate nonce, and once it has
            // been accepted the nonce will be incremented.
            uint256 private _nonce;
          
            // The self-call context flag is in storage slot 2. Some protected functions
            // may only be called externally from calls originating from other methods on
            // this contract, which enables appropriate exception handling on reverts.
            // Any storage should only be set immediately preceding a self-call and should
            // be cleared upon entering the protected function being called.
            bytes4 internal _selfCallContext;
          
            // END STORAGE DECLARATIONS - DO NOT REMOVE OR REORDER STORAGE ABOVE HERE!
          
            // The smart wallet version will be used when constructing valid signatures.
            uint256 internal constant _DHARMA_SMART_WALLET_VERSION = 14;
          
            // DharmaKeyRegistryV2 holds a public key for verifying meta-transactions.
            DharmaKeyRegistryInterface internal constant _DHARMA_KEY_REGISTRY = (
              DharmaKeyRegistryInterface(0x000000000D38df53b45C5733c7b34000dE0BDF52)
            );
          
            // Account recovery is facilitated using a hard-coded recovery manager,
            // controlled by Dharma and implementing appropriate timelocks.
            address internal constant _ACCOUNT_RECOVERY_MANAGER = address(
              0x0000000000DfEd903aD76996FC07BF89C0127B1E
            );
          
            // Users can designate an "escape hatch" account with the ability to sweep any
            // funds from their smart wallet by using the Dharma Escape Hatch Registry.
            DharmaEscapeHatchRegistryInterface internal constant _ESCAPE_HATCH_REGISTRY = (
              DharmaEscapeHatchRegistryInterface(0x00000000005280B515004B998a944630B6C663f8)
            );
          
            // Interface with dDai and dUSDC contracts.
            DTokenInterface internal constant _DDAI = DTokenInterface(
              0x00000000001876eB1444c986fD502e618c587430 // mainnet
            );
          
            DTokenInterface internal constant _DUSDC = DTokenInterface(
              0x00000000008943c65cAf789FFFCF953bE156f6f8 // mainnet
            );
          
            // The "revert reason helper" contains a collection of revert reason strings.
            RevertReasonHelperInterface internal constant _REVERT_REASON_HELPER = (
              RevertReasonHelperInterface(0x9C0ccB765D3f5035f8b5Dd30fE375d5F4997D8E4)
            );
          
            // The "Trade Bot" enables limit orders using unordered meta-transactions.
            address internal constant _TRADE_BOT = address(
              0x8bFB7aC05bF9bDC6Bc3a635d4dd209c8Ba39E554
            );
          
            // ERC-1271 must return this magic value when `isValidSignature` is called.
            bytes4 internal constant _ERC_1271_MAGIC_VALUE = bytes4(0x20c13b0b);
          
            // Specify the amount of gas to supply when making Ether transfers.
            uint256 private constant _ETH_TRANSFER_GAS = 4999;
          
            /**
             * @notice Accept Ether in the fallback.
             */
            function () external payable {}
          
            /**
             * @notice In the initializer, set up the initial user signing key. Note that
             * this initializer is only callable while the smart wallet instance is still
             * in the contract creation phase.
             * @param userSigningKey address The initial user signing key for the smart
             * wallet.
             */
            function initialize(address userSigningKey) external {
              // Ensure that this function is only callable during contract construction.
              assembly { if extcodesize(address) { revert(0, 0) } }
          
              // Set up the user's signing key and emit a corresponding event.
              _setUserSigningKey(userSigningKey);
            }
          
            /**
             * @notice Redeem all Dharma Dai held by this account for Dai.
             */
            function redeemAllDDai() external {
              _withdrawMaxFromDharmaToken(AssetType.DAI);
            }
          
            /**
             * @notice Redeem all Dharma USD Coin held by this account for USDC.
             */
            function redeemAllDUSDC() external {
              _withdrawMaxFromDharmaToken(AssetType.USDC);
            }
          
            /**
             * @notice This call is no longer supported.
             */
            function repayAndDeposit() external {
              revert("Deprecated.");
            }
          
            /**
             * @notice This call is no longer supported.
             */
            function withdrawDai(
              uint256 amount,
              address recipient,
              uint256 minimumActionGas,
              bytes calldata userSignature,
              bytes calldata dharmaSignature
            ) external returns (bool ok) {
              revert("Deprecated.");
            }
          
            /**
             * @notice This call is no longer supported.
             */
            function withdrawUSDC(
              uint256 amount,
              address recipient,
              uint256 minimumActionGas,
              bytes calldata userSignature,
              bytes calldata dharmaSignature
            ) external returns (bool ok) {
              revert("Deprecated.");
            }
          
            /**
             * @notice Withdraw Ether to a provided recipient address by transferring it
             * to a recipient.
             * @param amount uint256 The amount of Ether to withdraw.
             * @param recipient address The account to transfer the Ether to.
             * @param minimumActionGas uint256 The minimum amount of gas that must be
             * provided to this call - be aware that additional gas must still be included
             * to account for the cost of overhead incurred up until the start of this
             * function call.
             * @param userSignature bytes A signature that resolves to the public key
             * set for this account in storage slot zero, `_userSigningKey`. If the user
             * signing key is not a contract, ecrecover will be used; otherwise, ERC1271
             * will be used. A unique hash returned from `getCustomActionID` is prefixed
             * and hashed to create the message hash for the signature.
             * @param dharmaSignature bytes A signature that resolves to the public key
             * returned for this account from the Dharma Key Registry. A unique hash
             * returned from `getCustomActionID` is prefixed and hashed to create the
             * signed message.
             * @return True if the transfer succeeded, otherwise false.
             */
            function withdrawEther(
              uint256 amount,
              address payable recipient,
              uint256 minimumActionGas,
              bytes calldata userSignature,
              bytes calldata dharmaSignature
            ) external returns (bool ok) {
              // Ensure caller and/or supplied signatures are valid and increment nonce.
              _validateActionAndIncrementNonce(
                ActionType.ETHWithdrawal,
                abi.encode(amount, recipient),
                minimumActionGas,
                userSignature,
                dharmaSignature
              );
          
              // Ensure that a non-zero amount of Ether has been supplied.
              if (amount == 0) {
                revert(_revertReason(4));
              }
          
              // Ensure that a non-zero recipient has been supplied.
              if (recipient == address(0)) {
                revert(_revertReason(1));
              }
          
              // Attempt to transfer Ether to the recipient and emit an appropriate event.
              ok = _transferETH(recipient, amount);
            }
          
            /**
             * @notice Allow a signatory to increment the nonce at any point. The current
             * nonce needs to be provided as an argument to the signature so as not to
             * enable griefing attacks. All arguments can be omitted if called directly.
             * No value is returned from this function - it will either succeed or revert.
             * @param minimumActionGas uint256 The minimum amount of gas that must be
             * provided to this call - be aware that additional gas must still be included
             * to account for the cost of overhead incurred up until the start of this
             * function call.
             * @param signature bytes A signature that resolves to either the public key
             * set for this account in storage slot zero, `_userSigningKey`, or the public
             * key returned for this account from the Dharma Key Registry. A unique hash
             * returned from `getCustomActionID` is prefixed and hashed to create the
             * signed message.
             */
            function cancel(
              uint256 minimumActionGas,
              bytes calldata signature
            ) external {
              // Get the current nonce.
              uint256 nonceToCancel = _nonce;
          
              // Ensure the caller or the supplied signature is valid and increment nonce.
              _validateActionAndIncrementNonce(
                ActionType.Cancel,
                abi.encode(),
                minimumActionGas,
                signature,
                signature
              );
          
              // Emit an event to validate that the nonce is no longer valid.
              emit Cancel(nonceToCancel);
            }
          
            /**
             * @notice This call is no longer supported.
             */
            function executeAction(
              address to,
              bytes calldata data,
              uint256 minimumActionGas,
              bytes calldata userSignature,
              bytes calldata dharmaSignature
            ) external returns (bool ok, bytes memory returnData) {
              revert("Deprecated.");
            }
          
            /**
             * @notice Allow signatory to set a new user signing key. The current nonce
             * needs to be provided as an argument to the signature so as not to enable
             * griefing attacks. No value is returned from this function - it will either
             * succeed or revert.
             * @param userSigningKey address The new user signing key to set on this smart
             * wallet.
             * @param minimumActionGas uint256 The minimum amount of gas that must be
             * provided to this call - be aware that additional gas must still be included
             * to account for the cost of overhead incurred up until the start of this
             * function call.
             * @param userSignature bytes A signature that resolves to the public key
             * set for this account in storage slot zero, `_userSigningKey`. If the user
             * signing key is not a contract, ecrecover will be used; otherwise, ERC1271
             * will be used. A unique hash returned from `getCustomActionID` is prefixed
             * and hashed to create the message hash for the signature.
             * @param dharmaSignature bytes A signature that resolves to the public key
             * returned for this account from the Dharma Key Registry. A unique hash
             * returned from `getCustomActionID` is prefixed and hashed to create the
             * signed message.
             */
            function setUserSigningKey(
              address userSigningKey,
              uint256 minimumActionGas,
              bytes calldata userSignature,
              bytes calldata dharmaSignature
            ) external {
              // Ensure caller and/or supplied signatures are valid and increment nonce.
              _validateActionAndIncrementNonce(
                ActionType.SetUserSigningKey,
                abi.encode(userSigningKey),
                minimumActionGas,
                userSignature,
                dharmaSignature
              );
          
              // Set new user signing key on smart wallet and emit a corresponding event.
              _setUserSigningKey(userSigningKey);
            }
          
            /**
             * @notice Set a dedicated address as the "escape hatch" account. This account
             * can then call `escape(address token)` at any point to "sweep" the entire
             * balance of the token (or Ether given null address) from the smart wallet.
             * This function call will revert if the smart wallet has previously called
             * `permanentlyDisableEscapeHatch` at any point and disabled the escape hatch.
             * No value is returned from this function - it will either succeed or revert.
             * @param account address The account to set as the escape hatch account.
             * @param minimumActionGas uint256 The minimum amount of gas that must be
             * provided to this call - be aware that additional gas must still be included
             * to account for the cost of overhead incurred up until the start of this
             * function call.
             * @param userSignature bytes A signature that resolves to the public key
             * set for this account in storage slot zero, `_userSigningKey`. If the user
             * signing key is not a contract, ecrecover will be used; otherwise, ERC1271
             * will be used. A unique hash returned from `getCustomActionID` is prefixed
             * and hashed to create the message hash for the signature.
             * @param dharmaSignature bytes A signature that resolves to the public key
             * returned for this account from the Dharma Key Registry. A unique hash
             * returned from `getCustomActionID` is prefixed and hashed to create the
             * signed message.
             */
            function setEscapeHatch(
              address account,
              uint256 minimumActionGas,
              bytes calldata userSignature,
              bytes calldata dharmaSignature
            ) external {
              // Ensure caller and/or supplied signatures are valid and increment nonce.
              _validateActionAndIncrementNonce(
                ActionType.SetEscapeHatch,
                abi.encode(account),
                minimumActionGas,
                userSignature,
                dharmaSignature
              );
          
              // Ensure that an escape hatch account has been provided.
              if (account == address(0)) {
                revert(_revertReason(5));
              }
          
              // Set a new escape hatch for the smart wallet unless it has been disabled.
              _ESCAPE_HATCH_REGISTRY.setEscapeHatch(account);
            }
          
            /**
             * @notice Remove the "escape hatch" account if one is currently set. This
             * function call will revert if the smart wallet has previously called
             * `permanentlyDisableEscapeHatch` at any point and disabled the escape hatch.
             * No value is returned from this function - it will either succeed or revert.
             * @param minimumActionGas uint256 The minimum amount of gas that must be
             * provided to this call - be aware that additional gas must still be included
             * to account for the cost of overhead incurred up until the start of this
             * function call.
             * @param userSignature bytes A signature that resolves to the public key
             * set for this account in storage slot zero, `_userSigningKey`. If the user
             * signing key is not a contract, ecrecover will be used; otherwise, ERC1271
             * will be used. A unique hash returned from `getCustomActionID` is prefixed
             * and hashed to create the message hash for the signature.
             * @param dharmaSignature bytes A signature that resolves to the public key
             * returned for this account from the Dharma Key Registry. A unique hash
             * returned from `getCustomActionID` is prefixed and hashed to create the
             * signed message.
             */
            function removeEscapeHatch(
              uint256 minimumActionGas,
              bytes calldata userSignature,
              bytes calldata dharmaSignature
            ) external {
              // Ensure caller and/or supplied signatures are valid and increment nonce.
              _validateActionAndIncrementNonce(
                ActionType.RemoveEscapeHatch,
                abi.encode(),
                minimumActionGas,
                userSignature,
                dharmaSignature
              );
          
              // Remove the escape hatch for the smart wallet if one is currently set.
              _ESCAPE_HATCH_REGISTRY.removeEscapeHatch();
            }
          
            /**
             * @notice Permanently disable the "escape hatch" mechanism for this smart
             * wallet. This function call will revert if the smart wallet has already
             * called `permanentlyDisableEscapeHatch` at any point in the past. No value
             * is returned from this function - it will either succeed or revert.
             * @param minimumActionGas uint256 The minimum amount of gas that must be
             * provided to this call - be aware that additional gas must still be included
             * to account for the cost of overhead incurred up until the start of this
             * function call.
             * @param userSignature bytes A signature that resolves to the public key
             * set for this account in storage slot zero, `_userSigningKey`. If the user
             * signing key is not a contract, ecrecover will be used; otherwise, ERC1271
             * will be used. A unique hash returned from `getCustomActionID` is prefixed
             * and hashed to create the message hash for the signature.
             * @param dharmaSignature bytes A signature that resolves to the public key
             * returned for this account from the Dharma Key Registry. A unique hash
             * returned from `getCustomActionID` is prefixed and hashed to create the
             * signed message.
             */
            function permanentlyDisableEscapeHatch(
              uint256 minimumActionGas,
              bytes calldata userSignature,
              bytes calldata dharmaSignature
            ) external {
              // Ensure caller and/or supplied signatures are valid and increment nonce.
              _validateActionAndIncrementNonce(
                ActionType.DisableEscapeHatch,
                abi.encode(),
                minimumActionGas,
                userSignature,
                dharmaSignature
              );
          
              // Permanently disable the escape hatch mechanism for this smart wallet.
              _ESCAPE_HATCH_REGISTRY.permanentlyDisableEscapeHatch();
            }
          
            /**
             * @notice This call is no longer supported.
             */
            function tradeEthForDaiAndMintDDai(
              uint256 ethToSupply,
              uint256 minimumDaiReceived,
              address target,
              bytes calldata data,
              uint256 minimumActionGas,
              bytes calldata userSignature,
              bytes calldata dharmaSignature
            ) external returns (bool ok, bytes memory returnData) {
              revert("Deprecated.");
            }
          
            /**
             * @notice Allow the designated escape hatch account to redeem and "sweep"
             * the total token balance or Ether balance (by supplying the null address)
             * from the smart wallet. The call will revert for any other caller, or if
             * there is no escape hatch account on this smart wallet. An `Escaped` event
             * will be emitted. No value is returned from this function - it will either
             * succeed or revert.
             */
            function escape(address token) external {
              // Get the escape hatch account, if one exists, for this account.
              (bool exists, address escapeHatch) = _ESCAPE_HATCH_REGISTRY.getEscapeHatch();
          
              // Ensure that an escape hatch is currently set for this smart wallet.
              if (!exists) {
                revert(_revertReason(6));
              }
          
              // Ensure that the escape hatch account is the caller.
              if (msg.sender != escapeHatch) {
                revert(_revertReason(7));
              }
          
              if (token == address(0)) {
                // Determine if there is Ether at this address that should be transferred.
                uint256 balance = address(this).balance;
                if (balance > 0) {
                  // Attempt to transfer any Ether to caller and emit an appropriate event.
                  _transferETH(msg.sender, balance);
                }
              } else {
                // Attempt to transfer all tokens to the caller.
                _transferMax(ERC20Interface(address(token)), msg.sender, false);
              }
          
              // Emit an `Escaped` event.
              emit Escaped();
            }
          
            /**
             * @notice Allow the account recovery manager to set a new user signing key on
             * the smart wallet. The call will revert for any other caller. The account
             * recovery manager implements a set of controls around the process, including
             * a timelock and an option to permanently opt out of account recover. No
             * value is returned from this function - it will either succeed or revert.
             * @param newUserSigningKey address The new user signing key to set on this
             * smart wallet.
             */
            function recover(address newUserSigningKey) external {
              // Only the Account Recovery Manager contract may call this function.
              if (msg.sender != _ACCOUNT_RECOVERY_MANAGER) {
                revert(_revertReason(8));
              }
          
              // Increment nonce to prevent signature reuse should original key be reset.
              _nonce++;
          
              // Set up the user's new dharma key and emit a corresponding event.
              _setUserSigningKey(newUserSigningKey);
            }
          
            function setApproval(address token, uint256 amount) external {
              // Only the Trade Bot contract may call this function.
              if (msg.sender != _TRADE_BOT) {
                revert("Only the Trade Bot may call this function.");
              }
          
              ERC20Interface(token).approve(_TRADE_BOT, amount);
            }
          
            /**
             * @notice This call is no longer supported.
             */
            function migrateSaiToDai() external {
              revert("Deprecated.");
            }
          
            /**
             * @notice This call is no longer supported.
             */
            function migrateCSaiToDDai() external {
              revert("Deprecated.");
            }
          
            /**
             * @notice This call is no longer supported.
             */
            function migrateCDaiToDDai() external {
               revert("Deprecated.");
            }
          
            /**
             * @notice This call is no longer supported.
             */
            function migrateCUSDCToDUSDC() external {
               revert("Deprecated.");
            }
          
            /**
             * @notice This call is no longer supported.
             */
            function getBalances() external view returns (
              uint256 daiBalance,
              uint256 usdcBalance,
              uint256 etherBalance,
              uint256 dDaiUnderlyingDaiBalance,
              uint256 dUsdcUnderlyingUsdcBalance,
              uint256 dEtherUnderlyingEtherBalance // always returns 0
            ) {
              revert("Deprecated.");
            }
          
            /**
             * @notice View function for getting the current user signing key for the
             * smart wallet.
             * @return The current user signing key.
             */
            function getUserSigningKey() external view returns (address userSigningKey) {
              userSigningKey = _userSigningKey;
            }
          
            /**
             * @notice View function for getting the current nonce of the smart wallet.
             * This nonce is incremented whenever an action is taken that requires a
             * signature and/or a specific caller.
             * @return The current nonce.
             */
            function getNonce() external view returns (uint256 nonce) {
              nonce = _nonce;
            }
          
            /**
             * @notice View function that, given an action type and arguments, will return
             * the action ID or message hash that will need to be prefixed (according to
             * EIP-191 0x45), hashed, and signed by both the user signing key and by the
             * key returned for this smart wallet by the Dharma Key Registry in order to
             * construct a valid signature for the corresponding action. Any nonce value
             * may be supplied, which enables constructing valid message hashes for
             * multiple future actions ahead of time.
             * @param action uint8 The type of action, designated by it's index. Valid
             * custom actions include Cancel (0), SetUserSigningKey (1),
             * DAIWithdrawal (10), USDCWithdrawal (5), ETHWithdrawal (6),
             * SetEscapeHatch (7), RemoveEscapeHatch (8), and DisableEscapeHatch (9).
             * @param amount uint256 The amount to withdraw for Withdrawal actions. This
             * value is ignored for non-withdrawal action types.
             * @param recipient address The account to transfer withdrawn funds to or the
             * new user signing key. This value is ignored for Cancel, RemoveEscapeHatch,
             * and DisableEscapeHatch action types.
             * @param minimumActionGas uint256 The minimum amount of gas that must be
             * provided to this call - be aware that additional gas must still be included
             * to account for the cost of overhead incurred up until the start of this
             * function call.
             * @return The action ID, which will need to be prefixed, hashed and signed in
             * order to construct a valid signature.
             */
            function getNextCustomActionID(
              ActionType action,
              uint256 amount,
              address recipient,
              uint256 minimumActionGas
            ) external view returns (bytes32 actionID) {
              // Determine the actionID - this serves as a signature hash for an action.
              actionID = _getActionID(
                action,
                _validateCustomActionTypeAndGetArguments(action, amount, recipient),
                _nonce,
                minimumActionGas,
                _userSigningKey,
                _getDharmaSigningKey()
              );
            }
          
            /**
             * @notice View function that, given an action type and arguments, will return
             * the action ID or message hash that will need to be prefixed (according to
             * EIP-191 0x45), hashed, and signed by both the user signing key and by the
             * key returned for this smart wallet by the Dharma Key Registry in order to
             * construct a valid signature for the corresponding action. The current nonce
             * will be used, which means that it will only be valid for the next action
             * taken.
             * @param action uint8 The type of action, designated by it's index. Valid
             * custom actions include Cancel (0), SetUserSigningKey (1),
             * DAIWithdrawal (10), USDCWithdrawal (5), ETHWithdrawal (6),
             * SetEscapeHatch (7), RemoveEscapeHatch (8), and DisableEscapeHatch (9).
             * @param amount uint256 The amount to withdraw for Withdrawal actions. This
             * value is ignored for non-withdrawal action types.
             * @param recipient address The account to transfer withdrawn funds to or the
             * new user signing key. This value is ignored for Cancel, RemoveEscapeHatch,
             * and DisableEscapeHatch action types.
             * @param nonce uint256 The nonce to use.
             * @param minimumActionGas uint256 The minimum amount of gas that must be
             * provided to this call - be aware that additional gas must still be included
             * to account for the cost of overhead incurred up until the start of this
             * function call.
             * @return The action ID, which will need to be prefixed, hashed and signed in
             * order to construct a valid signature.
             */
            function getCustomActionID(
              ActionType action,
              uint256 amount,
              address recipient,
              uint256 nonce,
              uint256 minimumActionGas
            ) external view returns (bytes32 actionID) {
              // Determine the actionID - this serves as a signature hash for an action.
              actionID = _getActionID(
                action,
                _validateCustomActionTypeAndGetArguments(action, amount, recipient),
                nonce,
                minimumActionGas,
                _userSigningKey,
                _getDharmaSigningKey()
              );
            }
          
            /**
             * @notice This call is no longer supported.
             */
            function getNextGenericActionID(
              address to,
              bytes calldata data,
              uint256 minimumActionGas
            ) external view returns (bytes32 actionID) {
              revert("Deprecated.");
            }
          
            /**
             * @notice This call is no longer supported.
             */
            function getGenericActionID(
              address to,
              bytes calldata data,
              uint256 nonce,
              uint256 minimumActionGas
            ) external view returns (bytes32 actionID) {
              revert("Deprecated.");
            }
          
            /**
             * @notice This call is no longer supported.
             */
            function getNextEthForDaiActionID(
              uint256 ethToSupply,
              uint256 minimumDaiReceived,
              address target,
              bytes calldata data,
              uint256 minimumActionGas
            ) external view returns (bytes32 actionID) {
              revert("Deprecated.");
            }
          
            /**
             * @notice This call is no longer supported.
             */
            function getEthForDaiActionID(
              uint256 ethToSupply,
              uint256 minimumDaiReceived,
              address target,
              bytes calldata data,
              uint256 nonce,
              uint256 minimumActionGas
            ) external view returns (bytes32 actionID) {
              revert("Deprecated.");
            }
          
            /**
             * @notice View function that implements ERC-1271 and validates a set of
             * signatures, one from the owner (using ERC-1271 as well if the user signing
             * key is a contract) and one from the Dharma Key Registry against the
             * supplied data. The data must be ABI encoded as (bytes32, bytes), where the
             * first bytes32 parameter represents the hash digest for validating the
             * supplied signatures and the second bytes parameter contains context for the
             * requested validation. The two signatures are packed together, with the one
             * from Dharma coming first and that from the user coming second - this is so
             * that, in future versions, multiple user signatures may be supplied if the
             * associated key ring requires them.
             * @param data bytes The data used to validate the signature.
             * @param signatures bytes The two signatures, each 65 bytes - one from the
             * owner (using ERC-1271 as well if the user signing key is a contract) and
             * one from the Dharma Key Registry.
             * @return The 4-byte magic value to signify a valid signature in ERC-1271, if
             * the signatures are both valid.
             */
            function isValidSignature(
              bytes calldata data, bytes calldata signatures
            ) external view returns (bytes4 magicValue) {
              // Get message hash digest and any additional context from data argument.
              bytes32 digest;
              bytes memory context;
          
              if (data.length == 32) {
                digest = abi.decode(data, (bytes32));
              } else {
                if (data.length < 64) {
                  revert(_revertReason(30));
                }
                (digest, context) = abi.decode(data, (bytes32, bytes));
              }
          
              // Get Dharma signature & user signature from combined signatures argument.
              if (signatures.length != 130) {
                revert(_revertReason(11));
              }
              bytes memory signaturesInMemory = signatures;
              bytes32 r;
              bytes32 s;
              uint8 v;
              assembly {
                r := mload(add(signaturesInMemory, 0x20))
                s := mload(add(signaturesInMemory, 0x40))
                v := byte(0, mload(add(signaturesInMemory, 0x60)))
              }
              bytes memory dharmaSignature = abi.encodePacked(r, s, v);
          
              assembly {
                r := mload(add(signaturesInMemory, 0x61))
                s := mload(add(signaturesInMemory, 0x81))
                v := byte(0, mload(add(signaturesInMemory, 0xa1)))
              }
              bytes memory userSignature = abi.encodePacked(r, s, v);
          
              // Validate user signature with `SignatureVerification` as the action type.
              if (
                !_validateUserSignature(
                  digest,
                  ActionType.SignatureVerification,
                  context,
                  _userSigningKey,
                  userSignature
                )
              ) {
                revert(_revertReason(12));
              }
          
              // Recover Dharma signature against key returned from Dharma Key Registry.
              if (_getDharmaSigningKey() != digest.recover(dharmaSignature)) {
                revert(_revertReason(13));
              }
          
              // Return the ERC-1271 magic value to indicate success.
              magicValue = _ERC_1271_MAGIC_VALUE;
            }
          
            /**
             * @notice View function for getting the current Dharma Smart Wallet
             * implementation contract address set on the upgrade beacon.
             * @return The current Dharma Smart Wallet implementation contract.
             */
            function getImplementation() external view returns (address implementation) {
              (bool ok, bytes memory returnData) = address(
                0x000000000026750c571ce882B17016557279ADaa
              ).staticcall("");
              require(ok && returnData.length == 32, "Invalid implementation.");
              implementation = abi.decode(returnData, (address));
            }
          
            /**
             * @notice Pure function for getting the current Dharma Smart Wallet version.
             * @return The current Dharma Smart Wallet version.
             */
            function getVersion() external pure returns (uint256 version) {
              version = _DHARMA_SMART_WALLET_VERSION;
            }
          
            /**
             * @notice Perform a series of generic calls to other contracts. If any call
             * fails during execution, the preceding calls will be rolled back, but their
             * original return data will still be accessible. Calls that would otherwise
             * occur after the failed call will not be executed. Note that accounts with
             * no code may not be specified unless value is included, nor may the smart
             * wallet itself or the escape hatch registry. In order to increment the nonce
             * and invalidate the signatures, a call to this function with valid targets,
             * signatutes, and gas will always succeed. To determine whether each call
             * made as part of the action was successful or not, either the corresponding
             * return value or `CallSuccess` and `CallFailure` events can be used - note
             * that even calls that return a success status will be rolled back unless all
             * of the calls returned a success status. Finally, note that this function
             * must currently be implemented as a public function (instead of as an
             * external one) due to an ABIEncoderV2 `UnimplementedFeatureError`.
             * @param calls Call[] A struct containing the target, value, and calldata to
             * provide when making each call.
             * @param minimumActionGas uint256 The minimum amount of gas that must be
             * provided to this call - be aware that additional gas must still be included
             * to account for the cost of overhead incurred up until the start of this
             * function call.
             * @param userSignature bytes A signature that resolves to the public key
             * set for this account in storage slot zero, `_userSigningKey`. If the user
             * signing key is not a contract, ecrecover will be used; otherwise, ERC1271
             * will be used. A unique hash returned from `getCustomActionID` is prefixed
             * and hashed to create the message hash for the signature.
             * @param dharmaSignature bytes A signature that resolves to the public key
             * returned for this account from the Dharma Key Registry. A unique hash
             * returned from `getCustomActionID` is prefixed and hashed to create the
             * signed message.
             * @return An array of structs signifying the status of each call, as well as
             * any data returned from that call. Calls that are not executed will return
             * empty data.
             */
            function executeActionWithAtomicBatchCalls(
              Call[] memory calls,
              uint256 minimumActionGas,
              bytes memory userSignature,
              bytes memory dharmaSignature
            ) public returns (bool[] memory ok, bytes[] memory returnData) {
              // Ensure that each `to` address is a contract and is not this contract.
              for (uint256 i = 0; i < calls.length; i++) {
                if (calls[i].value == 0) {
                  _ensureValidGenericCallTarget(calls[i].to);
                }
              }
          
              // Ensure caller and/or supplied signatures are valid and increment nonce.
              (bytes32 actionID, uint256 nonce) = _validateActionAndIncrementNonce(
                ActionType.GenericAtomicBatch,
                abi.encode(calls),
                minimumActionGas,
                userSignature,
                dharmaSignature
              );
          
              // Note: from this point on, there are no reverts (apart from out-of-gas or
              // call-depth-exceeded) originating from this contract. However, one of the
              // calls may revert, in which case the function will return `false`, along
              // with the revert reason encoded as bytes, and fire an CallFailure event.
          
              // Specify length of returned values in order to work with them in memory.
              ok = new bool[](calls.length);
              returnData = new bytes[](calls.length);
          
              // Set self-call context to call _executeActionWithAtomicBatchCallsAtomic.
              _selfCallContext = this.executeActionWithAtomicBatchCalls.selector;
          
              // Make the atomic self-call - if any call fails, calls that preceded it
              // will be rolled back and calls that follow it will not be made.
              (bool externalOk, bytes memory rawCallResults) = address(this).call(
                abi.encodeWithSelector(
                  this._executeActionWithAtomicBatchCallsAtomic.selector, calls
                )
              );
          
              // Ensure that self-call context has been cleared.
              if (!externalOk) {
                delete _selfCallContext;
              }
          
              // Parse data returned from self-call into each call result and store / log.
              CallReturn[] memory callResults = abi.decode(rawCallResults, (CallReturn[]));
              for (uint256 i = 0; i < callResults.length; i++) {
                Call memory currentCall = calls[i];
          
                // Set the status and the return data / revert reason from the call.
                ok[i] = callResults[i].ok;
                returnData[i] = callResults[i].returnData;
          
                // Emit CallSuccess or CallFailure event based on the outcome of the call.
                if (callResults[i].ok) {
                  // Note: while the call succeeded, the action may still have "failed".
                  emit CallSuccess(
                    actionID,
                    !externalOk, // If another call failed this will have been rolled back
                    nonce,
                    currentCall.to,
                    uint256(currentCall.value),
                    currentCall.data,
                    callResults[i].returnData
                  );
                } else {
                  // Note: while the call failed, the nonce will still be incremented,
                  // which will invalidate all supplied signatures.
                  emit CallFailure(
                    actionID,
                    nonce,
                    currentCall.to,
                    uint256(currentCall.value),
                    currentCall.data,
                    _decodeRevertReason(callResults[i].returnData)
                  );
          
                  // exit early - any calls after the first failed call will not execute.
                  break;
                }
              }
            }
          
            /**
             * @notice Protected function that can only be called from
             * `executeActionWithAtomicBatchCalls` on this contract. It will attempt to
             * perform each specified call, populating the array of results as it goes,
             * unless a failure occurs, at which point it will revert and "return" the
             * array of results as revert data. Otherwise, it will simply return the array
             * upon successful completion of each call. Finally, note that this function
             * must currently be implemented as a public function (instead of as an
             * external one) due to an ABIEncoderV2 `UnimplementedFeatureError`.
             * @param calls Call[] A struct containing the target, value, and calldata to
             * provide when making each call.
             * @return An array of structs signifying the status of each call, as well as
             * any data returned from that call. Calls that are not executed will return
             * empty data. If any of the calls fail, the array will be returned as revert
             * data.
             */
            function _executeActionWithAtomicBatchCallsAtomic(
              Call[] memory calls
            ) public returns (CallReturn[] memory callResults) {
              // Ensure caller is this contract and self-call context is correctly set.
              _enforceSelfCallFrom(this.executeActionWithAtomicBatchCalls.selector);
          
              bool rollBack = false;
              callResults = new CallReturn[](calls.length);
          
              for (uint256 i = 0; i < calls.length; i++) {
                // Perform low-level call and set return values using result.
                (bool ok, bytes memory returnData) = calls[i].to.call.value(
                  uint256(calls[i].value)
                )(calls[i].data);
                callResults[i] = CallReturn({ok: ok, returnData: returnData});
                if (!ok) {
                  // Exit early - any calls after the first failed call will not execute.
                  rollBack = true;
                  break;
                }
              }
          
              if (rollBack) {
                // Wrap in length encoding and revert (provide bytes instead of a string).
                bytes memory callResultsBytes = abi.encode(callResults);
                assembly { revert(add(32, callResultsBytes), mload(callResultsBytes)) }
              }
            }
          
            /**
             * @notice Simulate a series of generic calls to other contracts. Signatures
             * are not required, but all calls will be rolled back (and calls will only be
             * simulated up until a failing call is encountered).
             * @param calls Call[] A struct containing the target, value, and calldata to
             * provide when making each call.
             * @return An array of structs signifying the status of each call, as well as
             * any data returned from that call. Calls that are not executed will return
             * empty data.
             */
            function simulateActionWithAtomicBatchCalls(
              Call[] memory calls
            ) public /* view */ returns (bool[] memory ok, bytes[] memory returnData) {
              // Ensure that each `to` address is a contract and is not this contract.
              for (uint256 i = 0; i < calls.length; i++) {
                if (calls[i].value == 0) {
                  _ensureValidGenericCallTarget(calls[i].to);
                }
              }
          
              // Specify length of returned values in order to work with them in memory.
              ok = new bool[](calls.length);
              returnData = new bytes[](calls.length);
          
              // Set self-call context to call _simulateActionWithAtomicBatchCallsAtomic.
              _selfCallContext = this.simulateActionWithAtomicBatchCalls.selector;
          
              // Make the atomic self-call - if any call fails, calls that preceded it
              // will be rolled back and calls that follow it will not be made.
              (bool mustBeFalse, bytes memory rawCallResults) = address(this).call(
                abi.encodeWithSelector(
                  this._simulateActionWithAtomicBatchCallsAtomic.selector, calls
                )
              );
          
              // Note: this should never be the case, but check just to be extra safe.
              if (mustBeFalse) {
                revert("Simulation call must revert!");
              }
          
              // Ensure that self-call context has been cleared.
              delete _selfCallContext;
          
              // Parse data returned from self-call into each call result and store / log.
              CallReturn[] memory callResults = abi.decode(rawCallResults, (CallReturn[]));
              for (uint256 i = 0; i < callResults.length; i++) {
                // Set the status and the return data / revert reason from the call.
                ok[i] = callResults[i].ok;
                returnData[i] = callResults[i].returnData;
          
                if (!callResults[i].ok) {
                  // exit early - any calls after the first failed call will not execute.
                  break;
                }
              }
            }
          
            /**
             * @notice Protected function that can only be called from
             * `simulateActionWithAtomicBatchCalls` on this contract. It will attempt to
             * perform each specified call, populating the array of results as it goes,
             * unless a failure occurs, at which point it will revert and "return" the
             * array of results as revert data. Regardless, it will roll back all calls at
             * the end of execution — in other words, this call always reverts.
             * @param calls Call[] A struct containing the target, value, and calldata to
             * provide when making each call.
             * @return An array of structs signifying the status of each call, as well as
             * any data returned from that call. Calls that are not executed will return
             * empty data. If any of the calls fail, the array will be returned as revert
             * data.
             */
            function _simulateActionWithAtomicBatchCallsAtomic(
              Call[] memory calls
            ) public returns (CallReturn[] memory callResults) {
              // Ensure caller is this contract and self-call context is correctly set.
              _enforceSelfCallFrom(this.simulateActionWithAtomicBatchCalls.selector);
          
              callResults = new CallReturn[](calls.length);
          
              for (uint256 i = 0; i < calls.length; i++) {
                // Perform low-level call and set return values using result.
                (bool ok, bytes memory returnData) = calls[i].to.call.value(
                  uint256(calls[i].value)
                )(calls[i].data);
                callResults[i] = CallReturn({ok: ok, returnData: returnData});
                if (!ok) {
                  // Exit early - any calls after the first failed call will not execute.
                  break;
                }
              }
          
              // Wrap in length encoding and revert (provide bytes instead of a string).
              bytes memory callResultsBytes = abi.encode(callResults);
              assembly { revert(add(32, callResultsBytes), mload(callResultsBytes)) }
            }
          
            /**
             * @notice View function that, given an action type and arguments, will return
             * the action ID or message hash that will need to be prefixed (according to
             * EIP-191 0x45), hashed, and signed by both the user signing key and by the
             * key returned for this smart wallet by the Dharma Key Registry in order to
             * construct a valid signature for a given generic atomic batch action. The
             * current nonce will be used, which means that it will only be valid for the
             * next action taken. Finally, note that this function must currently be
             * implemented as a public function (instead of as an external one) due to an
             * ABIEncoderV2 `UnimplementedFeatureError`.
             * @param calls Call[] A struct containing the target and calldata to provide
             * when making each call.
             * @param calls Call[] A struct containing the target and calldata to provide
             * when making each call.
             * @param minimumActionGas uint256 The minimum amount of gas that must be
             * provided to this call - be aware that additional gas must still be included
             * to account for the cost of overhead incurred up until the start of this
             * function call.
             * @return The action ID, which will need to be prefixed, hashed and signed in
             * order to construct a valid signature.
             */
            function getNextGenericAtomicBatchActionID(
              Call[] memory calls,
              uint256 minimumActionGas
            ) public view returns (bytes32 actionID) {
              // Determine the actionID - this serves as a signature hash for an action.
              actionID = _getActionID(
                ActionType.GenericAtomicBatch,
                abi.encode(calls),
                _nonce,
                minimumActionGas,
                _userSigningKey,
                _getDharmaSigningKey()
              );
            }
          
            /**
             * @notice View function that, given an action type and arguments, will return
             * the action ID or message hash that will need to be prefixed (according to
             * EIP-191 0x45), hashed, and signed by both the user signing key and by the
             * key returned for this smart wallet by the Dharma Key Registry in order to
             * construct a valid signature for a given generic atomic batch action. Any
             * nonce value may be supplied, which enables constructing valid message
             * hashes for multiple future actions ahead of time. Finally, note that this
             * function must currently be implemented as a public function (instead of as
             * an external one) due to an ABIEncoderV2 `UnimplementedFeatureError`.
             * @param calls Call[] A struct containing the target and calldata to provide
             * when making each call.
             * @param calls Call[] A struct containing the target and calldata to provide
             * when making each call.
             * @param nonce uint256 The nonce to use.
             * @param minimumActionGas uint256 The minimum amount of gas that must be
             * provided to this call - be aware that additional gas must still be included
             * to account for the cost of overhead incurred up until the start of this
             * function call.
             * @return The action ID, which will need to be prefixed, hashed and signed in
             * order to construct a valid signature.
             */
            function getGenericAtomicBatchActionID(
              Call[] memory calls,
              uint256 nonce,
              uint256 minimumActionGas
            ) public view returns (bytes32 actionID) {
              // Determine the actionID - this serves as a signature hash for an action.
              actionID = _getActionID(
                ActionType.GenericAtomicBatch,
                abi.encode(calls),
                nonce,
                minimumActionGas,
                _userSigningKey,
                _getDharmaSigningKey()
              );
            }
          
            /**
             * @notice Internal function for setting a new user signing key. Called by the
             * initializer, by the `setUserSigningKey` function, and by the `recover`
             * function. A `NewUserSigningKey` event will also be emitted.
             * @param userSigningKey address The new user signing key to set on this smart
             * wallet.
             */
            function _setUserSigningKey(address userSigningKey) internal {
              // Ensure that a user signing key is set on this smart wallet.
              if (userSigningKey == address(0)) {
                revert(_revertReason(14));
              }
          
              _userSigningKey = userSigningKey;
              emit NewUserSigningKey(userSigningKey);
            }
          
            /**
             * @notice Internal function for withdrawing the total underlying asset
             * balance from the corresponding dToken. Note that the requested balance may
             * not be currently available on Compound, which will cause the withdrawal to
             * fail.
             * @param asset uint256 The asset's ID, either Dai (0) or USDC (1).
             */
            function _withdrawMaxFromDharmaToken(AssetType asset) internal {
              // Get dToken address for the asset type. (No custom ETH withdrawal action.)
              address dToken = asset == AssetType.DAI ? address(_DDAI) : address(_DUSDC);
          
              // Try to retrieve the current dToken balance for this account.
              ERC20Interface dTokenBalance;
              (bool ok, bytes memory data) = dToken.call(abi.encodeWithSelector(
                dTokenBalance.balanceOf.selector, address(this)
              ));
          
              uint256 redeemAmount = 0;
              if (ok && data.length == 32) {
                redeemAmount = abi.decode(data, (uint256));
              } else {
                // Something went wrong with the balance check - log an ExternalError.
                _checkDharmaTokenInteractionAndLogAnyErrors(
                  asset, dTokenBalance.balanceOf.selector, ok, data
                );
              }
          
              // Only perform the call to redeem if there is a non-zero balance.
              if (redeemAmount > 0) {
                // Attempt to redeem the underlying balance from the dToken contract.
                (ok, data) = dToken.call(abi.encodeWithSelector(
                  // Function selector is the same for all dTokens, so just use dDai's.
                  _DDAI.redeem.selector, redeemAmount
                ));
          
                // Log an external error if something went wrong with the attempt.
                _checkDharmaTokenInteractionAndLogAnyErrors(
                  asset, _DDAI.redeem.selector, ok, data
                );
              }
            }
          
            /**
             * @notice Internal function for transferring the total underlying balance of
             * the corresponding token to a designated recipient. It will return true if
             * tokens were successfully transferred (or there is no balance), signified by
             * the boolean returned by the transfer function, or the call status if the
             * `suppressRevert` boolean is set to true.
             * @param token IERC20 The interface of the token in question.
             * @param recipient address The account that will receive the tokens.
             * @param suppressRevert bool A boolean indicating whether reverts should be
             * suppressed or not. Used by the escape hatch so that a problematic transfer
             * will not block the rest of the call from executing.
             * @return True if tokens were successfully transferred or if there is no
             * balance, else false.
             */
            function _transferMax(
              ERC20Interface token, address recipient, bool suppressRevert
            ) internal returns (bool success) {
              // Get the current balance on the smart wallet for the supplied ERC20 token.
              uint256 balance = 0;
              bool balanceCheckWorked = true;
              if (!suppressRevert) {
                balance = token.balanceOf(address(this));
              } else {
                // Try to retrieve current token balance for this account with 1/2 gas.
                (bool ok, bytes memory data) = address(token).call.gas(gasleft() / 2)(
                  abi.encodeWithSelector(token.balanceOf.selector, address(this))
                );
          
                if (ok && data.length >= 32) {
                  balance = abi.decode(data, (uint256));
                } else {
                  // Something went wrong with the balance check.
                  balanceCheckWorked = false;
                }
              }
          
              // Only perform the call to transfer if there is a non-zero balance.
              if (balance > 0) {
                if (!suppressRevert) {
                  // Perform the transfer and pass along the returned boolean (or revert).
                  success = token.transfer(recipient, balance);
                } else {
                  // Attempt transfer with 1/2 gas, allow reverts, and return call status.
                  (success, ) = address(token).call.gas(gasleft() / 2)(
                    abi.encodeWithSelector(token.transfer.selector, recipient, balance)
                  );
                }
              } else {
                // Skip the transfer and return true as long as the balance check worked.
                success = balanceCheckWorked;
              }
            }
          
            /**
             * @notice Internal function for transferring Ether to a designated recipient.
             * It will return true and emit an `EthWithdrawal` event if Ether was
             * successfully transferred - otherwise, it will return false and emit an
             * `ExternalError` event.
             * @param recipient address payable The account that will receive the Ether.
             * @param amount uint256 The amount of Ether to transfer.
             * @return True if Ether was successfully transferred, else false.
             */
            function _transferETH(
              address payable recipient, uint256 amount
            ) internal returns (bool success) {
              // Attempt to transfer any Ether to caller and emit an event if it fails.
              (success, ) = recipient.call.gas(_ETH_TRANSFER_GAS).value(amount)("");
              if (!success) {
                emit ExternalError(recipient, _revertReason(18));
              } else {
                emit EthWithdrawal(amount, recipient);
              }
            }
          
            /**
             * @notice Internal function for validating supplied gas (if specified),
             * retrieving the signer's public key from the Dharma Key Registry, deriving
             * the action ID, validating the provided caller and/or signatures using that
             * action ID, and incrementing the nonce. This function serves as the
             * entrypoint for all protected "actions" on the smart wallet, and is the only
             * area where these functions should revert (other than due to out-of-gas
             * errors, which can be guarded against by supplying a minimum action gas
             * requirement).
             * @param action uint8 The type of action, designated by it's index. Valid
             * actions include Cancel (0), SetUserSigningKey (1), Generic (2),
             * GenericAtomicBatch (3), DAIWithdrawal (10), USDCWithdrawal (5),
             * ETHWithdrawal (6), SetEscapeHatch (7), RemoveEscapeHatch (8), and
             * DisableEscapeHatch (9).
             * @param arguments bytes ABI-encoded arguments for the action.
             * @param minimumActionGas uint256 The minimum amount of gas that must be
             * provided to this call - be aware that additional gas must still be included
             * to account for the cost of overhead incurred up until the start of this
             * function call.
             * @param userSignature bytes A signature that resolves to the public key
             * set for this account in storage slot zero, `_userSigningKey`. If the user
             * signing key is not a contract, ecrecover will be used; otherwise, ERC1271
             * will be used. A unique hash returned from `getCustomActionID` is prefixed
             * and hashed to create the message hash for the signature.
             * @param dharmaSignature bytes A signature that resolves to the public key
             * returned for this account from the Dharma Key Registry. A unique hash
             * returned from `getCustomActionID` is prefixed and hashed to create the
             * signed message.
             * @return The nonce of the current action (prior to incrementing it).
             */
            function _validateActionAndIncrementNonce(
              ActionType action,
              bytes memory arguments,
              uint256 minimumActionGas,
              bytes memory userSignature,
              bytes memory dharmaSignature
            ) internal returns (bytes32 actionID, uint256 actionNonce) {
              // Ensure that the current gas exceeds the minimum required action gas.
              // This prevents griefing attacks where an attacker can invalidate a
              // signature without providing enough gas for the action to succeed. Also
              // note that some gas will be spent before this check is reached - supplying
              // ~30,000 additional gas should suffice when submitting transactions. To
              // skip this requirement, supply zero for the minimumActionGas argument.
              if (minimumActionGas != 0) {
                if (gasleft() < minimumActionGas) {
                  revert(_revertReason(19));
                }
              }
          
              // Get the current nonce for the action to be performed.
              actionNonce = _nonce;
          
              // Get the user signing key that will be used to verify their signature.
              address userSigningKey = _userSigningKey;
          
              // Get the Dharma signing key that will be used to verify their signature.
              address dharmaSigningKey = _getDharmaSigningKey();
          
              // Determine the actionID - this serves as the signature hash.
              actionID = _getActionID(
                action,
                arguments,
                actionNonce,
                minimumActionGas,
                userSigningKey,
                dharmaSigningKey
              );
          
              // Compute the message hash - the hashed, EIP-191-0x45-prefixed action ID.
              bytes32 messageHash = actionID.toEthSignedMessageHash();
          
              // Actions other than Cancel require both signatures; Cancel only needs one.
              if (action != ActionType.Cancel) {
                // Validate user signing key signature unless it is `msg.sender`.
                if (msg.sender != userSigningKey) {
                  if (
                    !_validateUserSignature(
                      messageHash, action, arguments, userSigningKey, userSignature
                    )
                  ) {
                    revert(_revertReason(20));
                  }
                }
          
                // Validate Dharma signing key signature unless it is `msg.sender`.
                if (msg.sender != dharmaSigningKey) {
                  if (dharmaSigningKey != messageHash.recover(dharmaSignature)) {
                    revert(_revertReason(21));
                  }
                }
              } else {
                // Validate signing key signature unless user or Dharma is `msg.sender`.
                if (msg.sender != userSigningKey && msg.sender != dharmaSigningKey) {
                  if (
                    dharmaSigningKey != messageHash.recover(dharmaSignature) &&
                    !_validateUserSignature(
                      messageHash, action, arguments, userSigningKey, userSignature
                    )
                  ) {
                    revert(_revertReason(22));
                  }
                }
              }
          
              // Increment nonce in order to prevent reuse of signatures after the call.
              _nonce++;
            }
          
            /**
             * @notice Internal function to determine whether a call to a given dToken
             * succeeded, and to emit a relevant ExternalError event if it failed.
             * @param asset uint256 The ID of the asset, either Dai (0) or USDC (1).
             * @param functionSelector bytes4 The function selector that was called on the
             * corresponding dToken of the asset type.
             * @param ok bool A boolean representing whether the call returned or
             * reverted.
             * @param data bytes The data provided by the returned or reverted call.
             * @return True if the interaction was successful, otherwise false. This will
             * be used to determine if subsequent steps in the action should be attempted
             * or not, specifically a transfer following a withdrawal.
             */
            function _checkDharmaTokenInteractionAndLogAnyErrors(
              AssetType asset,
              bytes4 functionSelector,
              bool ok,
              bytes memory data
            ) internal returns (bool success) {
              // Log an external error if something went wrong with the attempt.
              if (ok) {
                if (data.length == 32) {
                  uint256 amount = abi.decode(data, (uint256));
                  if (amount > 0) {
                    success = true;
                  } else {
                    // Get called contract address, name of contract, and function name.
                    (address account, string memory name, string memory functionName) = (
                      _getDharmaTokenDetails(asset, functionSelector)
                    );
          
                    emit ExternalError(
                      account,
                      string(
                        abi.encodePacked(
                          name,
                          " gave no tokens calling ",
                          functionName,
                          "."
                        )
                      )
                    );
                  }
                } else {
                  // Get called contract address, name of contract, and function name.
                  (address account, string memory name, string memory functionName) = (
                    _getDharmaTokenDetails(asset, functionSelector)
                  );
          
                  emit ExternalError(
                    account,
                    string(
                      abi.encodePacked(
                        name,
                        " gave bad data calling ",
                        functionName,
                        "."
                      )
                    )
                  );
                }
          
              } else {
                // Get called contract address, name of contract, and function name.
                (address account, string memory name, string memory functionName) = (
                  _getDharmaTokenDetails(asset, functionSelector)
                );
          
                // Decode the revert reason in the event one was returned.
                string memory revertReason = _decodeRevertReason(data);
          
                emit ExternalError(
                  account,
                  string(
                    abi.encodePacked(
                      name,
                      " reverted calling ",
                      functionName,
                      ": ",
                      revertReason
                    )
                  )
                );
              }
            }
          
            /**
             * @notice Internal function to ensure that protected functions can only be
             * called from this contract and that they have the appropriate context set.
             * The self-call context is then cleared. It is used as an additional guard
             * against reentrancy, especially once generic actions are supported by the
             * smart wallet in future versions.
             * @param selfCallContext bytes4 The expected self-call context, equal to the
             * function selector of the approved calling function.
             */
            function _enforceSelfCallFrom(bytes4 selfCallContext) internal {
              // Ensure caller is this contract and self-call context is correctly set.
              if (msg.sender != address(this) || _selfCallContext != selfCallContext) {
                revert(_revertReason(25));
              }
          
              // Clear the self-call context.
              delete _selfCallContext;
            }
          
            /**
             * @notice Internal view function for validating a user's signature. If the
             * user's signing key does not have contract code, it will be validated via
             * ecrecover; otherwise, it will be validated using ERC-1271, passing the
             * message hash that was signed, the action type, and the arguments as data.
             * @param messageHash bytes32 The message hash that is signed by the user. It
             * is derived by prefixing (according to EIP-191 0x45) and hashing an actionID
             * returned from `getCustomActionID`.
             * @param action uint8 The type of action, designated by it's index. Valid
             * actions include Cancel (0), SetUserSigningKey (1), Generic (2),
             * GenericAtomicBatch (3), DAIWithdrawal (10), USDCWithdrawal (5),
             * ETHWithdrawal (6), SetEscapeHatch (7), RemoveEscapeHatch (8), and
             * DisableEscapeHatch (9).
             * @param arguments bytes ABI-encoded arguments for the action.
             * @param userSignature bytes A signature that resolves to the public key
             * set for this account in storage slot zero, `_userSigningKey`. If the user
             * signing key is not a contract, ecrecover will be used; otherwise, ERC1271
             * will be used.
             * @return A boolean representing the validity of the supplied user signature.
             */
            function _validateUserSignature(
              bytes32 messageHash,
              ActionType action,
              bytes memory arguments,
              address userSigningKey,
              bytes memory userSignature
            ) internal view returns (bool valid) {
              if (!userSigningKey.isContract()) {
                valid = userSigningKey == messageHash.recover(userSignature);
              } else {
                bytes memory data = abi.encode(messageHash, action, arguments);
                valid = (
                  ERC1271Interface(userSigningKey).isValidSignature(
                    data, userSignature
                  ) == _ERC_1271_MAGIC_VALUE
                );
              }
            }
          
            /**
             * @notice Internal view function to get the Dharma signing key for the smart
             * wallet from the Dharma Key Registry. This key can be set for each specific
             * smart wallet - if none has been set, a global fallback key will be used.
             * @return The address of the Dharma signing key, or public key corresponding
             * to the secondary signer.
             */
            function _getDharmaSigningKey() internal view returns (
              address dharmaSigningKey
            ) {
              dharmaSigningKey = _DHARMA_KEY_REGISTRY.getKey();
            }
          
            /**
             * @notice Internal view function that, given an action type and arguments,
             * will return the action ID or message hash that will need to be prefixed
             * (according to EIP-191 0x45), hashed, and signed by the key designated by
             * the Dharma Key Registry in order to construct a valid signature for the
             * corresponding action. The current nonce will be supplied to this function
             * when reconstructing an action ID during protected function execution based
             * on the supplied parameters.
             * @param action uint8 The type of action, designated by it's index. Valid
             * actions include Cancel (0), SetUserSigningKey (1), Generic (2),
             * GenericAtomicBatch (3), DAIWithdrawal (10), USDCWithdrawal (5),
             * ETHWithdrawal (6), SetEscapeHatch (7), RemoveEscapeHatch (8), and
             * DisableEscapeHatch (9).
             * @param arguments bytes ABI-encoded arguments for the action.
             * @param nonce uint256 The nonce to use.
             * @param minimumActionGas uint256 The minimum amount of gas that must be
             * provided to this call - be aware that additional gas must still be included
             * to account for the cost of overhead incurred up until the start of this
             * function call.
             * @param dharmaSigningKey address The address of the secondary key, or public
             * key corresponding to the secondary signer.
             * @return The action ID, which will need to be prefixed, hashed and signed in
             * order to construct a valid signature.
             */
            function _getActionID(
              ActionType action,
              bytes memory arguments,
              uint256 nonce,
              uint256 minimumActionGas,
              address userSigningKey,
              address dharmaSigningKey
            ) internal view returns (bytes32 actionID) {
              // actionID is constructed according to EIP-191-0x45 to prevent replays.
              actionID = keccak256(
                abi.encodePacked(
                  address(this),
                  _DHARMA_SMART_WALLET_VERSION,
                  userSigningKey,
                  dharmaSigningKey,
                  nonce,
                  minimumActionGas,
                  action,
                  arguments
                )
              );
            }
          
            /**
             * @notice Internal pure function to get the dToken address, it's name, and
             * the name of the called function, based on a supplied asset type and
             * function selector. It is used to help construct ExternalError events.
             * @param asset uint256 The ID of the asset, either Dai (0) or USDC (1).
             * @param functionSelector bytes4 The function selector that was called on the
             * corresponding dToken of the asset type.
             * @return The dToken address, it's name, and the name of the called function.
             */
            function _getDharmaTokenDetails(
              AssetType asset,
              bytes4 functionSelector
            ) internal pure returns (
              address account,
              string memory name,
              string memory functionName
            ) {
              if (asset == AssetType.DAI) {
                account = address(_DDAI);
                name = "Dharma Dai";
              } else {
                account = address(_DUSDC);
                name = "Dharma USD Coin";
              }
          
              // Note: since both dTokens have the same interface, just use dDai's.
              if (functionSelector == _DDAI.mint.selector) {
                functionName = "mint";
              } else {
                if (functionSelector == ERC20Interface(account).balanceOf.selector) {
                  functionName = "balanceOf";
                } else {
                  functionName = string(abi.encodePacked(
                    "redeem",
                    functionSelector == _DDAI.redeem.selector ? "" : "Underlying"
                  ));
                }
              }
            }
          
            /**
             * @notice Internal view function to ensure that a given `to` address provided
             * as part of a generic action is valid. Calls cannot be performed to accounts
             * without code or back into the smart wallet itself. Additionally, generic
             * calls cannot supply the address of the Dharma Escape Hatch registry - the
             * specific, designated functions must be used in order to make calls into it.
             * @param to address The address that will be targeted by the generic call.
             */
            function _ensureValidGenericCallTarget(address to) internal view {
              if (!to.isContract()) {
                revert(_revertReason(26));
              }
          
              if (to == address(this)) {
                revert(_revertReason(27));
              }
          
              if (to == address(_ESCAPE_HATCH_REGISTRY)) {
                revert(_revertReason(28));
              }
            }
          
            /**
             * @notice Internal pure function to ensure that a given action type is a
             * "custom" action type (i.e. is not a generic action type) and to construct
             * the "arguments" input to an actionID based on that action type.
             * @param action uint8 The type of action, designated by it's index. Valid
             * custom actions include Cancel (0), SetUserSigningKey (1),
             * DAIWithdrawal (10), USDCWithdrawal (5), ETHWithdrawal (6),
             * SetEscapeHatch (7), RemoveEscapeHatch (8), and DisableEscapeHatch (9).
             * @param amount uint256 The amount to withdraw for Withdrawal actions. This
             * value is ignored for all non-withdrawal action types.
             * @param recipient address The account to transfer withdrawn funds to or the
             * new user signing key. This value is ignored for Cancel, RemoveEscapeHatch,
             * and DisableEscapeHatch action types.
             * @return A bytes array containing the arguments that will be provided as
             * a component of the inputs when constructing a custom action ID.
             */
            function _validateCustomActionTypeAndGetArguments(
              ActionType action, uint256 amount, address recipient
            ) internal pure returns (bytes memory arguments) {
              // Ensure that the action type is a valid custom action type.
              bool validActionType = (
                action == ActionType.Cancel ||
                action == ActionType.SetUserSigningKey ||
                action == ActionType.DAIWithdrawal ||
                action == ActionType.USDCWithdrawal ||
                action == ActionType.ETHWithdrawal ||
                action == ActionType.SetEscapeHatch ||
                action == ActionType.RemoveEscapeHatch ||
                action == ActionType.DisableEscapeHatch
              );
              if (!validActionType) {
                revert(_revertReason(29));
              }
          
              // Use action type to determine parameters to include in returned arguments.
              if (
                action == ActionType.Cancel ||
                action == ActionType.RemoveEscapeHatch ||
                action == ActionType.DisableEscapeHatch
              ) {
                // Ignore parameters for Cancel, RemoveEscapeHatch, or DisableEscapeHatch.
                arguments = abi.encode();
              } else if (
                action == ActionType.SetUserSigningKey ||
                action == ActionType.SetEscapeHatch
              ) {
                // Ignore `amount` parameter for other, non-withdrawal actions.
                arguments = abi.encode(recipient);
              } else {
                // Use both `amount` and `recipient` parameters for withdrawals.
                arguments = abi.encode(amount, recipient);
              }
            }
          
            /**
             * @notice Internal pure function to decode revert reasons. The revert reason
             * prefix is removed and the remaining string argument is decoded.
             * @param revertData bytes The raw data supplied alongside the revert.
             * @return The decoded revert reason string.
             */
            function _decodeRevertReason(
              bytes memory revertData
            ) internal pure returns (string memory revertReason) {
              // Solidity prefixes revert reason with 0x08c379a0 -> Error(string) selector
              if (
                revertData.length > 68 && // prefix (4) + position (32) + length (32)
                revertData[0] == byte(0x08) &&
                revertData[1] == byte(0xc3) &&
                revertData[2] == byte(0x79) &&
                revertData[3] == byte(0xa0)
              ) {
                // Get the revert reason without the prefix from the revert data.
                bytes memory revertReasonBytes = new bytes(revertData.length - 4);
                for (uint256 i = 4; i < revertData.length; i++) {
                  revertReasonBytes[i - 4] = revertData[i];
                }
          
                // Decode the resultant revert reason as a string.
                revertReason = abi.decode(revertReasonBytes, (string));
              } else {
                // Simply return the default, with no revert reason.
                revertReason = _revertReason(uint256(-1));
              }
            }
          
            /**
             * @notice Internal pure function call the revert reason helper contract,
             * supplying a revert "code" and receiving back a revert reason string.
             * @param code uint256 The code for the revert reason.
             * @return The revert reason string.
             */
            function _revertReason(
              uint256 code
            ) internal pure returns (string memory reason) {
              reason = _REVERT_REASON_HELPER.reason(code);
            }
          }

          File 4 of 6: DharmaKeyRegistryV2
          pragma solidity 0.5.11; // optimization runs: 200, evm version: petersburg
          
          
          interface DharmaKeyRegistryInterface {
            event NewGlobalKey(address oldGlobalKey, address newGlobalKey);
            event NewSpecificKey(
              address indexed account, address oldSpecificKey, address newSpecificKey
            );
          
            function setGlobalKey(address globalKey, bytes calldata signature) external;
            function setSpecificKey(address account, address specificKey) external;
            function getKey() external view returns (address key);
            function getKeyForUser(address account) external view returns (address key);
            function getGlobalKey() external view returns (address globalKey);
            function getSpecificKey(address account) external view returns (address specificKey);
          }
          
          
          library ECDSA {
              function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
                  if (signature.length != 65) {
                      return (address(0));
                  }
          
                  bytes32 r;
                  bytes32 s;
                  uint8 v;
          
                  assembly {
                      r := mload(add(signature, 0x20))
                      s := mload(add(signature, 0x40))
                      v := byte(0, mload(add(signature, 0x60)))
                  }
          
                  if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
                      return address(0);
                  }
          
                  if (v != 27 && v != 28) {
                      return address(0);
                  }
          
                  return ecrecover(hash, v, r, s);
              }
          
              function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
                  return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
              }
          }
          
          
          /**
           * @dev Contract module which provides a basic access control mechanism, where
           * there is an account (an owner) that can be granted exclusive access to
           * specific functions.
           *
           * This module is used through inheritance. It will make available the modifier
           * `onlyOwner`, which can be aplied to your functions to restrict their use to
           * the owner.
           *
           * In order to transfer ownership, a recipient must be specified, at which point
           * the specified recipient can call `acceptOwnership` and take ownership.
           */
          contract TwoStepOwnable {
            address private _owner;
          
            address private _newPotentialOwner;
          
            event OwnershipTransferred(
              address indexed previousOwner,
              address indexed newOwner
            );
          
            /**
             * @dev Initialize contract by setting transaction submitter as initial owner.
             */
            constructor() internal {
              _owner = tx.origin;
              emit OwnershipTransferred(address(0), _owner);
            }
          
            /**
             * @dev Returns the address of the current owner.
             */
            function owner() public view returns (address) {
              return _owner;
            }
          
            /**
             * @dev Throws if called by any account other than the owner.
             */
            modifier onlyOwner() {
              require(isOwner(), "TwoStepOwnable: caller is not the owner.");
              _;
            }
          
            /**
             * @dev Returns true if the caller is the current owner.
             */
            function isOwner() public view returns (bool) {
              return msg.sender == _owner;
            }
          
            /**
             * @dev Allows a new account (`newOwner`) to accept ownership.
             * Can only be called by the current owner.
             */
            function transferOwnership(address newOwner) public onlyOwner {
              require(
                newOwner != address(0),
                "TwoStepOwnable: new potential owner is the zero address."
              );
          
              _newPotentialOwner = newOwner;
            }
          
            /**
             * @dev Cancel a transfer of ownership to a new account.
             * Can only be called by the current owner.
             */
            function cancelOwnershipTransfer() public onlyOwner {
              delete _newPotentialOwner;
            }
          
            /**
             * @dev Transfers ownership of the contract to the caller.
             * Can only be called by a new potential owner set by the current owner.
             */
            function acceptOwnership() public {
              require(
                msg.sender == _newPotentialOwner,
                "TwoStepOwnable: current owner must set caller as new potential owner."
              );
          
              delete _newPotentialOwner;
          
              emit OwnershipTransferred(_owner, msg.sender);
          
              _owner = msg.sender;
            }
          }
          
          
          /**
           * @title DharmaKeyRegistryV2
           * @author 0age
           * @notice The Dharma Key Registry is an owned contract that holds the public
           * user signing keys that will be used by the Dharma Smart Wallet. Each time a
           * particular Dharma Smart Wallet instance needs to validate a signature, it
           * will first retrieve the public address for the secondary signing key
           * associated with that wallet from the Dharma Key Registry. If a specific key
           * has not been set for that smart wallet, it will return the global public key.
           * Otherwise, it will return the specific signing key. Additional view functions
           * are also provided for retrieving public keys directly. Only the owner may
           * update these keys. Also, note that the V2 key registry includes an additional
           * mapping to track all keys that have been used, and only allows a given key to
           * be set one time.
           */
          contract DharmaKeyRegistryV2 is TwoStepOwnable, DharmaKeyRegistryInterface {
            using ECDSA for bytes32;
          
            // The global public key serves as the default signing key.
            address private _globalKey;
          
            // Specific keys may also be set on a per-caller basis.
            mapping (address => address) private _specificKeys;
          
            // Maintain a mapping of all used keys (to prevent reuse).
            mapping (address => bool) private _usedKeys;
          
            /**
             * @notice In the constructor, set the initial global key and the initial
             * owner to tx.origin.
             */
            constructor() public {
              // Initially set the global key to the account of the transaction submitter.
              _registerGlobalKey(tx.origin);
            }
          
            /**
             * @notice Set a new global key. This method may only be called by the owner,
             * and a signature must also be provided in order to verify that the provided
             * global public key has a corresponding private key that can be used to sign
             * messages.
             * @param globalKey address The new global public key.
             * @param signature bytes A signature of a message hash containing the address
             * of this contract, the new global key, and a specific message, that must
             * resolve to the supplied global key.
             */
            function setGlobalKey(
              address globalKey, bytes calldata signature
            ) external onlyOwner {
              // Ensure that the provided global key is not the null address.
              require(globalKey != address(0), "A global key must be supplied.");
          
              // Message hash constructed according to EIP-191-0x45 to prevent replays.
              bytes32 messageHash = keccak256(
                abi.encodePacked(
                  address(this),
                  globalKey,
                  "This signature demonstrates that the supplied signing key is valid."
                )
              );
          
              // Recover the signer of the message hash using the provided signature.
              address signer = messageHash.toEthSignedMessageHash().recover(signature);
          
              // Ensure that the provided signature resolves to the provided global key.
              require(globalKey == signer, "Invalid signature for supplied global key.");
          
              // Update global key to the provided global key and prevent future reuse.
              _registerGlobalKey(globalKey);
            }
          
            /**
             * @notice Set a new specific key for a particular account. This method may
             * only be called by the owner. Signatures are not required in order to make
             * setting specific keys more efficient at scale. Providing the null address
             * for the specific key will remove a specific key from the given account.
             * @param account address The account to set the new specific public key for.
             * @param specificKey address The new specific public key.
             */
            function setSpecificKey(
              address account, address specificKey
            ) external onlyOwner {
              // Ensure that the key has not been used previously.
              require(!_usedKeys[specificKey], "Key has been used previously.");
          
              // Emit an event signifying that the specific key has been modified.
              emit NewSpecificKey(account, _specificKeys[account], specificKey);
          
              // Update specific key for provided account to the provided specific key.
              _specificKeys[account] = specificKey;
          
              // Mark the key as having been used previously.
              _usedKeys[specificKey] = true;
            }
          
            /**
             * @notice Get the public key associated with the caller of this function. If
             * a specific key is set for the caller, it will be returned; otherwise, the
             * global key will be returned.
             * @return The public key to use for the caller.
             */
            function getKey() external view returns (address key) {
              // Retrieve the specific key, if any, for the caller.
              key = _specificKeys[msg.sender];
          
              // Fall back to the global key in the event that no specific key is set.
              if (key == address(0)) {
                key = _globalKey;
              }
            }
          
            /**
             * @notice Get the public key associated with a particular account. If a
             * specific key is set for the account, it will be returned; otherwise, the
             * global key will be returned.
             * @param account address The account to find the public key for.
             * @return The public key to use for the provided account.
             */
            function getKeyForUser(address account) external view returns (address key) {
              // Retrieve the specific key, if any, for the specified account.
              key = _specificKeys[account];
          
              // Fall back to the global key in the event that no specific key is set.
              if (key == address(0)) {
                key = _globalKey;
              }
            }
          
            /**
             * @notice Get the global public key.
             * @return The global public key.
             */
            function getGlobalKey() external view returns (address globalKey) {
              // Retrieve and return the global key.
              globalKey = _globalKey;
            }
          
            /**
             * @notice Get the specific public key associated with the supplied account.
             * The call will revert if a specific public key is not set for the account.
             * @param account address The account to find the specific public key for.
             * @return The specific public key set on the provided account, if one exists.
             */
            function getSpecificKey(
              address account
            ) external view returns (address specificKey) {
              // Retrieve the specific key, if any, for the account.
              specificKey = _specificKeys[account];
          
              // Revert in the event that there is no specific key set.
              require(
                specificKey != address(0),
                "No specific key set for the provided account."
              );
            }
          
            /**
             * @notice Internal function to set a new global key once contract ownership
             * and signature validity have both been checked, or during contract creation.
             * The provided global key must not have been used previously, and once set it
             * will be registered as having been used.
             * @param globalKey address The new global public key.
             */
            function _registerGlobalKey(address globalKey) internal {
              // Ensure that the key has not been used previously.
              require(!_usedKeys[globalKey], "Key has been used previously.");
          
              // Emit an event signifying that the global key has been modified.
              emit NewGlobalKey(_globalKey, globalKey);
          
              // Update the global key to the provided global key.
              _globalKey = globalKey;
          
              // Mark the key as having been used previously.
              _usedKeys[globalKey] = true;
            }
          }

          File 5 of 6: DharmaKeyRingUpgradeBeacon
          pragma solidity 0.5.11;
          
          
          /**
           * @title DharmaKeyRingUpgradeBeacon
           * @author 0age
           * @notice This contract holds the address of the current implementation for
           * Dharma key rings and lets a controller update that address in storage.
           */
          contract DharmaKeyRingUpgradeBeacon {
            // The implementation address is held in storage slot zero.
            address private _implementation;
          
            // The controller that can update the implementation is set as a constant.
            address private constant _CONTROLLER = address(
              0x00000000011dF015e8aD00D7B2486a88C2Eb8210
            );
          
            /**
             * @notice In the fallback function, allow only the controller to update the
             * implementation address - for all other callers, return the current address.
             * Note that this requires inline assembly, as Solidity fallback functions do
             * not natively take arguments or return values.
             */
            function () external {
              // Return implementation address for all callers other than the controller.
              if (msg.sender != _CONTROLLER) {
                // Load implementation from storage slot zero into memory and return it.
                assembly {
                  mstore(0, sload(0))
                  return(0, 32)
                }
              } else {
                // Set implementation - put first word in calldata in storage slot zero.
                assembly { sstore(0, calldataload(0)) }
              }
            }
          }

          File 6 of 6: DharmaKeyRingImplementationV1
          pragma solidity 0.5.11; // optimization runs: 200, evm version: petersburg
          
          
          interface DharmaKeyRingImplementationV0Interface {
            event KeyModified(address indexed key, bool standard, bool admin);
          
            enum KeyType {
              None,
              Standard,
              Admin,
              Dual
            }
          
            enum AdminActionType {
              AddStandardKey,
              RemoveStandardKey,
              SetStandardThreshold,
              AddAdminKey,
              RemoveAdminKey,
              SetAdminThreshold,
              AddDualKey,
              RemoveDualKey,
              SetDualThreshold
            }
          
            struct AdditionalKeyCount {
              uint128 standard;
              uint128 admin;
            }
          
            function takeAdminAction(
              AdminActionType adminActionType, uint160 argument, bytes calldata signatures
            ) external;
          
            function getAdminActionID(
              AdminActionType adminActionType, uint160 argument, uint256 nonce
            ) external view returns (bytes32 adminActionID);
          
            function getNextAdminActionID(
              AdminActionType adminActionType, uint160 argument
            ) external view returns (bytes32 adminActionID);
          
            function getKeyCount() external view returns (
              uint256 standardKeyCount, uint256 adminKeyCount
            );
          
            function getKeyType(
              address key
            ) external view returns (bool standard, bool admin);
          
            function getNonce() external returns (uint256 nonce);
          
            function getVersion() external pure returns (uint256 version);
          }
          
          
          interface ERC1271 {
            /**
             * @dev Should return whether the signature provided is valid for the provided data
             * @param data Arbitrary length data signed on the behalf of address(this)
             * @param signature Signature byte array associated with data
             *
             * MUST return the bytes4 magic value 0x20c13b0b when function passes.
             * MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5)
             * MUST allow external calls
             */ 
            function isValidSignature(
              bytes calldata data, 
              bytes calldata signature
            ) external view returns (bytes4 magicValue);
          }
          
          
          library ECDSA {
            function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
              if (signature.length != 65) {
                return (address(0));
              }
          
              bytes32 r;
              bytes32 s;
              uint8 v;
          
              assembly {
                r := mload(add(signature, 0x20))
                s := mload(add(signature, 0x40))
                v := byte(0, mload(add(signature, 0x60)))
              }
          
              if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
                return address(0);
              }
          
              if (v != 27 && v != 28) {
                return address(0);
              }
          
              return ecrecover(hash, v, r, s);
            }
          
            function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
              return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
            }
          }
          
          
          /**
           * @title DharmaKeyRingImplementationV1
           * @author 0age
           * @notice The Dharma Key Ring is a smart contract that implements ERC-1271 and
           * can be used in place of an externally-owned account for the user signing key
           * on the Dharma Smart Wallet to support multiple user signing keys. For this V1
           * implementation, new Dual keys (standard + admin) can be added, but cannot be
           * removed, and the action threshold is fixed at one. Upgrades are managed by an
           * upgrade beacon, similar to the one utilized by the Dharma Smart Wallet. Note
           * that this implementation only implements the minimum feature set required to
           * support multiple user signing keys on the current Dharma Smart Wallet, and
           * that it will likely be replaced with a new, more full-featured implementation
           * relatively soon. V1 differs from V0 in that it requires that an adminActionID
           * must be prefixed (according to EIP-191 0x45) and hashed in order to construct
           * a valid signature (note that the message hash given to `isValidSignature` is
           * assumed to have already been appropriately constructed to fit the caller's
           * requirements and so does not apply an additional prefix).
           */
          contract DharmaKeyRingImplementationV1 is
            DharmaKeyRingImplementationV0Interface,
            ERC1271 {
            using ECDSA for bytes32;
            // WARNING: DO NOT REMOVE OR REORDER STORAGE WHEN WRITING NEW IMPLEMENTATIONS!
          
            // Track all keys as an address (as uint160) => key type mapping in slot zero.
            mapping (uint160 => KeyType) private _keys;
          
            // Track the nonce in slot 1 so that actions cannot be replayed. Note that
            // proper nonce management must be managed by the implementing contract when
            // using `isValidSignature`, as it is a static method and cannot change state.
            uint256 private _nonce;
          
            // Track the total number of standard and admin keys in storage slot 2.
            AdditionalKeyCount private _additionalKeyCounts;
          
            // Track the required threshold standard and admin actions in storage slot 3.
            // AdditionalThreshold private _additionalThresholds;
          
            // END STORAGE DECLARATIONS - DO NOT REMOVE OR REORDER STORAGE ABOVE HERE!
          
            // The key ring version will be used when constructing valid signatures.
            uint256 internal constant _DHARMA_KEY_RING_VERSION = 1;
          
            // ERC-1271 must return this magic value when `isValidSignature` is called.
            bytes4 internal constant _ERC_1271_MAGIC_VALUE = bytes4(0x20c13b0b);
          
            /**
             * @notice In initializer, set up an initial user signing key. For V1, the
             * adminThreshold and executorThreshold arguments must both be equal to 1 and
             * exactly one key with a key type of 3 (Dual key) must be supplied. Note that
             * this initializer is only callable while the key ring instance is still in
             * the contract creation phase.
             * @param adminThreshold uint128 Must be equal to 1 in V1.
             * @param executorThreshold uint128 Must be equal to 1 in V1.
             * @param keys address[] The initial user signing key for the key ring. Must
             * have exactly one non-null key in V1.
             * @param keyTypes uint8[] Must be equal to [3].
             */
            function initialize(
              uint128 adminThreshold,
              uint128 executorThreshold,
              address[] calldata keys,
              uint8[] calldata keyTypes // must all be 3 (Dual) for V1
            ) external {
              // Ensure that this function is only callable during contract construction.
              assembly { if extcodesize(address) { revert(0, 0) } }
          
              // V1 only allows setting a singly Dual key with thresholds both set to 1.
              require(keys.length == 1, "Must supply exactly one key in V1.");
          
              require(keys[0] != address(0), "Cannot supply the null address as a key.");
          
              require(
                keyTypes.length == 1 && keyTypes[0] == uint8(3),
                "Must supply exactly one Dual keyType (3) in V1."
              );
          
              require(adminThreshold == 1, "Admin threshold must be exactly one in V1.");
          
              require(
                executorThreshold == 1, "Executor threshold must be exactly one in V1."
              );
          
              // Set the key and emit a corresponding event.
              _keys[uint160(keys[0])] = KeyType.Dual;
              emit KeyModified(keys[0], true, true);
          
              // Note: skip additional key counts + thresholds setup in V1 (only one key).
            }
          
            /**
             * @notice Supply a signature from one of the existing keys on the keyring in
             * order to add a new key.
             * @param adminActionType uint8 Must be equal to 6 in V1.
             * @param argument uint160 The signing address to add to the key ring.
             * @param signatures bytes A signature from an existing key on the key ring.
             */
            function takeAdminAction(
              AdminActionType adminActionType, uint160 argument, bytes calldata signatures
            ) external {
              // Only Admin Action Type 6 (AddDualKey) is supported in V1.
              require(
                adminActionType == AdminActionType.AddDualKey,
                "Only adding new Dual key types (admin action type 6) is supported in V1."
              );
          
              require(argument != uint160(0), "Cannot supply the null address as a key.");
          
              require(_keys[argument] == KeyType.None, "Key already exists.");
          
              // Verify signature against a hash of the prefixed admin admin actionID.
              _verifySignature(
                _getAdminActionID(argument, _nonce).toEthSignedMessageHash(), signatures
              );
          
              // Increment the key count for both standard and admin keys.
              _additionalKeyCounts.standard++;
              _additionalKeyCounts.admin++;
          
              // Set the key and emit a corresponding event.
              _keys[argument] = KeyType.Dual;
              emit KeyModified(address(argument), true, true);
          
              // Increment the nonce.
              _nonce++;
            }
          
            /**
             * @notice View function that implements ERC-1271 and validates a signature
             * against one of the keys on the keyring based on the supplied data. The data
             * must be ABI encoded as (bytes32, uint8, bytes) - in V1, only the first
             * bytes32 parameter is used to validate the supplied signature.
             * @param data bytes The data used to validate the signature.
             * @param signature bytes A signature from an existing key on the key ring.
             * @return The 4-byte magic value to signify a valid signature in ERC-1271, if
             * the signature is valid.
             */
            function isValidSignature(
              bytes calldata data, bytes calldata signature
            ) external view returns (bytes4 magicValue) {
              (bytes32 hash, , ) = abi.decode(data, (bytes32, uint8, bytes));
          
              _verifySignature(hash, signature);
          
              magicValue = _ERC_1271_MAGIC_VALUE;
            }
          
            /**
             * @notice View function that returns the message hash that must be signed in
             * order to add a new key to the key ring based on the supplied parameters.
             * @param adminActionType uint8 Unused in V1, as only action type 6 is valid.
             * @param argument uint160 The signing address to add to the key ring.
             * @param nonce uint256 The nonce to use when deriving the message hash.
             * @return The message hash to sign.
             */
            function getAdminActionID(
              AdminActionType adminActionType, uint160 argument, uint256 nonce
            ) external view returns (bytes32 adminActionID) {
              adminActionType;
              adminActionID = _getAdminActionID(argument, nonce);
            }
          
            /**
             * @notice View function that returns the message hash that must be signed in
             * order to add a new key to the key ring based on the supplied parameters and
             * using the current nonce of the key ring.
             * @param adminActionType uint8 Unused in V1, as only action type 6 is valid.
             * @param argument uint160 The signing address to add to the key ring.
             * @return The message hash to sign.
             */
            function getNextAdminActionID(
              AdminActionType adminActionType, uint160 argument
            ) external view returns (bytes32 adminActionID) {
              adminActionType;
              adminActionID = _getAdminActionID(argument, _nonce);
            }
          
            /**
             * @notice Pure function for getting the current Dharma Key Ring version.
             * @return The current Dharma Key Ring version.
             */
            function getVersion() external pure returns (uint256 version) {
              version = _DHARMA_KEY_RING_VERSION;
            }
          
            /**
             * @notice View function for getting the current number of both standard and
             * admin keys that are set on the Dharma Key Ring. For V1, these should be the
             * same value as one another.
             * @return The number of standard and admin keys set on the Dharma Key Ring.
             */
            function getKeyCount() external view returns (
              uint256 standardKeyCount, uint256 adminKeyCount
            ) {
              AdditionalKeyCount memory additionalKeyCount = _additionalKeyCounts;
              standardKeyCount = uint256(additionalKeyCount.standard) + 1;
              adminKeyCount = uint256(additionalKeyCount.admin) + 1;
            }
          
            /**
             * @notice View function for getting standard and admin key status of a given
             * address. For V1, these should both be true, or both be false (i.e. the key
             * is not set).
             * @param key address An account to check for key type information.
             * @return Booleans for standard and admin key status for the given address.
             */
            function getKeyType(
              address key
            ) external view returns (bool standard, bool admin) {
              KeyType keyType = _keys[uint160(key)];
              standard = (keyType == KeyType.Standard || keyType == KeyType.Dual);
              admin = (keyType == KeyType.Admin || keyType == KeyType.Dual);
            }
          
            /**
             * @notice View function for getting the current nonce of the Dharma Key Ring.
             * @return The current nonce set on the Dharma Key Ring.
             */
            function getNonce() external returns (uint256 nonce) {
              nonce = _nonce;
            }
          
            /**
             * @notice Internal view function to derive an action ID that is prefixed,
             * hashed, and signed by an existing key in order to add a new key to the key
             * ring.
             * @param argument uint160 The signing address to add to the key ring.
             * @param nonce uint256 The nonce to use when deriving the adminActionID.
             * @return The message hash to sign.
             */
            function _getAdminActionID(
              uint160 argument, uint256 nonce
            ) internal view returns (bytes32 adminActionID) {
              adminActionID = keccak256(
                abi.encodePacked(
                  address(this), _DHARMA_KEY_RING_VERSION, nonce, argument
                )
              );
            }
          
            /**
             * @notice Internal view function for verifying a signature and a message hash
             * against the mapping of keys currently stored on the key ring. For V1, all
             * stored keys are the Dual key type, and only a single signature is provided
             * for verification at once since the threshold is fixed at one signature.
             */
            function _verifySignature(
              bytes32 hash, bytes memory signature
            ) internal view {
              require(
                _keys[uint160(hash.recover(signature))] == KeyType.Dual,
                "Supplied signature does not have a signer with the required key type."
              );
            }
          }