Transaction Hash:
Block:
10622692 at Aug-09-2020 01:27:13 AM +UTC
Transaction Fee:
0.015063477 ETH
$31.93
Gas Used:
271,414 Gas / 55.5 Gwei
Emitted Events:
| 27 |
EtherizerV2.Approval( owner=[Receiver] 0xde1a6ec525733559660333e51856d69bd393a0b8, spender=DharmaTradeReserve, value=1000000000000000000 )
|
| 28 |
EtherizerV2.Approval( owner=[Receiver] 0xde1a6ec525733559660333e51856d69bd393a0b8, spender=DharmaTradeReserve, value=0 )
|
| 29 |
DharmaTradeReserve.0x1e57e3bb474320be3d2c77138f75b7c3941292d647f5f9634e33a8e94e0e069b( 0x1e57e3bb474320be3d2c77138f75b7c3941292d647f5f9634e33a8e94e0e069b, 000000000000000000000000de1a6ec525733559660333e51856d69bd393a0b8, 0000000000000000000000000000000000000000000000000de0b6b3a7640000 )
|
| 30 |
EtherizerV2.Transfer( from=[Receiver] 0xde1a6ec525733559660333e51856d69bd393a0b8, to=DharmaTradeReserve, value=1000000000000000000 )
|
| 31 |
WETH9.Deposit( dst=UniswapV2Router02, wad=989999999986100852 )
|
| 32 |
WETH9.Transfer( src=UniswapV2Router02, dst=UniswapV2Pair, wad=989999999986100852 )
|
| 33 |
OrionToken.Transfer( from=UniswapV2Pair, to=[Receiver] 0xde1a6ec525733559660333e51856d69bd393a0b8, value=9571088386 )
|
| 34 |
UniswapV2Pair.Sync( reserve0=4534027622766, reserve1=468567051214137840581 )
|
| 35 |
UniswapV2Pair.Swap( sender=UniswapV2Router02, amount0In=0, amount1In=989999999986100852, amount0Out=9571088386, amount1Out=0, to=[Receiver] 0xde1a6ec525733559660333e51856d69bd393a0b8 )
|
| 36 |
DharmaTradeReserve.0x1e57e3bb474320be3d2c77138f75b7c3941292d647f5f9634e33a8e94e0e069b( 0x1e57e3bb474320be3d2c77138f75b7c3941292d647f5f9634e33a8e94e0e069b, 0000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488d, 000000000000000000000000000000000000000000000000002386f27095158c )
|
| 37 |
DharmaTradeReserve.0x8d4e5e4cf68c4b7be730141ed4b1c725966c0e1df450c1a01b965afa060fa3e0( 0x8d4e5e4cf68c4b7be730141ed4b1c725966c0e1df450c1a01b965afa060fa3e0, 000000000000000000000000de1a6ec525733559660333e51856d69bd393a0b8, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000008fb00fdebb4e83f2c58b3bcd6732ac1b6a7b7221, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000de0b6b3a7640000, 000000000000000000000000000000000000000000000000000000023a7b3802, 000000000000000000000000000000000000000000000000002386f27095158c )
|
| 38 |
0xde1a6ec525733559660333e51856d69bd393a0b8.0xb4a7946c0772f99411b9461db61ae510087dfb48bc453a75b0b9915588f0c5cc( 0xb4a7946c0772f99411b9461db61ae510087dfb48bc453a75b0b9915588f0c5cc, 0b187b364a1d66ba1601fbe5aec4c7e314f2b8b75fea2d15ddb14b19156944c8, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000002, 000000000000000000000000723b51b72ae89a3d0c2a2760f0458307a1baa191, 00000000000000000000000000000000000000000000000000000000000000c0, 0000000000000000000000000000000000000000000000000000000000000140, 0000000000000000000000000000000000000000000000000000000000000044, 395093510000000000000000000000000efb068354c10c070ddd64a0e8eaf8f0, 54df7e260000000000000000000000000000000000000000000000000de0b6b3, a764000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000020, 0000000000000000000000000000000000000000000000000000000000000001 )
|
| 39 |
0xde1a6ec525733559660333e51856d69bd393a0b8.0xb4a7946c0772f99411b9461db61ae510087dfb48bc453a75b0b9915588f0c5cc( 0xb4a7946c0772f99411b9461db61ae510087dfb48bc453a75b0b9915588f0c5cc, 0b187b364a1d66ba1601fbe5aec4c7e314f2b8b75fea2d15ddb14b19156944c8, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000002, 0000000000000000000000000efb068354c10c070ddd64a0e8eaf8f054df7e26, 00000000000000000000000000000000000000000000000000000000000000c0, 0000000000000000000000000000000000000000000000000000000000000180, 0000000000000000000000000000000000000000000000000000000000000084, 68defc0c0000000000000000000000008fb00fdebb4e83f2c58b3bcd6732ac1b, 6a7b72210000000000000000000000000000000000000000000000000de0b6b3, a764000000000000000000000000000000000000000000000000000000000002, 3a7b380200000000000000000000000000000000000000000000000000000000, 5f2f558400000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000020, 0000000000000000000000000000000000000000000000000dbd2fc136ceea74 )
|
Account State Difference:
| Address | Before | After | State Difference | ||
|---|---|---|---|---|---|
| 0x0eFb0683...054DF7E26 | 100.313761373431053845 Eth | 100.323761373444952993 Eth | 0.010000000013899148 | ||
|
0x84A0d77c...010577051
Miner
| 534.353524796898016795 Eth | 534.368588273898016795 Eth | 0.015063477 | ||
| 0x8fB00FDe...B6A7b7221 | |||||
| 0xA4723f39...36dc0d84E |
3.020922928729 Eth
Nonce: 5729
|
3.005859451729 Eth
Nonce: 5730
| 0.015063477 | ||
| 0xbb59AAc3...9dc8f9F7C | |||||
| 0xC02aaA39...83C756Cc2 | 3,270,295.431011370255018925 Eth | 3,270,296.421011370241119777 Eth | 0.989999999986100852 | ||
| 0xDe1A6Ec5...Bd393a0b8 | 1 Eth | 0 Eth | 1 |
Execution Trace
0xde1a6ec525733559660333e51856d69bd393a0b8.168d88b9( )
-
DharmaUpgradeBeacon.STATICCALL( )
DharmaSmartWalletImplementationV11.executeActionWithAtomicBatchCalls( calls=, minimumActionGas=0, userSignature=0x7DB59E3E27CDB459F5BB45E300B2D5EC2001B97DBA40FB55D6F708360F61A87228A1F3D0FFDC93BCDFF0D82B82B643C08ED38891F7FC17B8636F033DA9DF35681B, dharmaSignature=0x06E13F7A5B48F89E872AC6CD94376881DBD1DC0EDC3AAF31352BF818E04DEA4C47E9FDD14E3051BACF60C7B5D8AC16B84D13F20B930F75F9E86AAFC8DBC32F081C ) => ( ok=[true, true], returnData=[AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADb0vwTbO6nQ=] )-
DharmaKeyRegistryV2.STATICCALL( ) 0x912468e455a3e068ea65cd4675777ca1d4e88ff9.20c13b0b( )-
DharmaKeyRingUpgradeBeacon.STATICCALL( )
DharmaKeyRingImplementationV1.isValidSignature( data=0xF03663F8792F122A8A5C0BCDAFFC11B49EAD2F801F03EB8E647A88D7FCCC10500000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000100000000000000000000000000723B51B72AE89A3D0C2A2760F0458307A1BAA19100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000044395093510000000000000000000000000EFB068354C10C070DDD64A0E8EAF8F054DF7E260000000000000000000000000000000000000000000000000DE0B6B3A7640000000000000000000000000000000000000000000000000000000000000000000000000000000000000EFB068354C10C070DDD64A0E8EAF8F054DF7E260000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008468DEFC0C0000000000000000000000008FB00FDEBB4E83F2C58B3BCD6732AC1B6A7B72210000000000000000000000000000000000000000000000000DE0B6B3A7640000000000000000000000000000000000000000000000000000000000023A7B3802000000000000000000000000000000000000000000000000000000005F2F558400000000000000000000000000000000000000000000000000000000, signature=0x7DB59E3E27CDB459F5BB45E300B2D5EC2001B97DBA40FB55D6F708360F61A87228A1F3D0FFDC93BCDFF0D82B82B643C08ED38891F7FC17B8636F033DA9DF35681B ) => ( data=0x, signature=0x )-
Null: 0x000...001.f03663f8( )
-
-
-
Null: 0x000...001.f03663f8( ) 0xde1a6ec525733559660333e51856d69bd393a0b8.ec9e14d4( )-
DharmaUpgradeBeacon.STATICCALL( )
DharmaSmartWalletImplementationV11._executeActionWithAtomicBatchCallsAtomic( calls= ) => ( callResults= )-
EtherizerV2.increaseAllowance( spender=0x0eFb068354c10c070ddD64a0E8EaF8f054DF7E26, addedValue=1000000000000000000 ) => ( success=True )
DharmaTradeReserve.68defc0c( )-
DharmaTradeReserveUpgradeBeacon.STATICCALL( )
DharmaTradeReserveV13Implementation.tradeEtherForTokenUsingEtherizer( token=0x8fB00FDeBb4E83f2C58b3bcD6732AC1B6A7b7221, etherAmount=1000000000000000000, quotedTokenAmount=9571088386, deadline=1596937604 ) => ( totalEtherSold=989999999986100852 )-
EtherizerV2.transferFrom( owner=0xDe1A6Ec525733559660333e51856D69Bd393a0b8, recipient=0x0eFb068354c10c070ddD64a0E8EaF8f054DF7E26, amount=1000000000000000000 ) => ( success=True )
- ETH 1
UniswapV2Router02.swapETHForExactTokens( amountOut=9571088386, path=[0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, 0x8fB00FDeBb4E83f2C58b3bcD6732AC1B6A7b7221], to=0xDe1A6Ec525733559660333e51856D69Bd393a0b8, deadline=1596937604 ) => ( amounts=[989999999986100852, 9571088386] )
-
-
-
-
-
File 1 of 13: EtherizerV2
File 2 of 13: DharmaTradeReserve
File 3 of 13: WETH9
File 4 of 13: UniswapV2Router02
File 5 of 13: UniswapV2Pair
File 6 of 13: OrionToken
File 7 of 13: DharmaUpgradeBeacon
File 8 of 13: DharmaSmartWalletImplementationV11
File 9 of 13: DharmaKeyRegistryV2
File 10 of 13: DharmaKeyRingUpgradeBeacon
File 11 of 13: DharmaKeyRingImplementationV1
File 12 of 13: DharmaTradeReserveUpgradeBeacon
File 13 of 13: DharmaTradeReserveV13Implementation
pragma solidity 0.6.8; // optimization runs: 200, evm version: istanbul
interface ERC1271Interface {
function isValidSignature(
bytes calldata data, bytes calldata signatures
) external view returns (bytes4 magicValue);
}
interface EtherizedInterface {
function triggerEtherTransfer(
address target, uint256 value
) external returns (bool success);
}
interface EtherizerV2Interface {
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
function transferFrom(
EtherizedInterface from, address payable to, uint256 value
) external returns (bool success);
function approve(address spender, uint256 value) external returns (bool success);
function increaseAllowance(
address spender, uint256 addedValue
) external returns (bool success);
function decreaseAllowance(
address spender, uint256 subtractedValue
) external returns (bool success);
function modifyAllowanceViaMetaTransaction(
address owner,
address spender,
uint256 value,
bool increase,
uint256 expiration,
bytes32 salt,
bytes calldata signatures
) external returns (bool success);
function cancelAllowanceModificationMetaTransaction(
address owner,
address spender,
uint256 value,
bool increase,
uint256 expiration,
bytes32 salt
) external returns (bool success);
function getMetaTransactionMessageHash(
bytes4 functionSelector, bytes calldata arguments, uint256 expiration, bytes32 salt
) external view returns (bytes32 digest, bool valid);
function balanceOf(address account) external view returns (uint256 amount);
function allowance(address owner, address spender) external view returns (uint256 amount);
}
library SafeMath {
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a, "SafeMath: subtraction overflow");
uint256 c = a - b;
return c;
}
}
/**
* @title EtherizerV2
* @author 0age
* @notice Etherizer is a contract for enabling "approvals" for performing
* Ether transfers from compliant accounts via either standard ERC20 methods
* or meta-transactions. A "compliant" account must be a smart contract that
* implements a `triggerEtherTransfer` function that is only callable by this
* contract as well as the `isValidSignature` function specified by ERC-1271
* for enabling meta-transaction functionality. Be warned that any approved
* spender can initiate calls from the owner's account — though data will be
* empty, considerable caution is still merited when granting any approvals.
*/
contract EtherizerV2 is EtherizerV2Interface {
using SafeMath for uint256;
// Maintain a mapping of Ether allowances.
mapping (address => mapping (address => uint256)) private _allowances;
// Maintain a mapping of invalid meta-transaction message hashes.
mapping (bytes32 => bool) private _invalidMetaTxHashes;
/**
* @notice Trigger a call from `owner` to `recipient` with `amount`
* Ether as long as `msg.sender` has sufficient allowance, `owner` is a
* compliant contract that implements `triggerEtherTransfer`, and
* `recipient` is able to receive Ether transfers.
* @param owner address The account to perform the call from.
* @param recipient address The account to call.
* @param amount uint256 The amount of Ether to transfer.
* @return success A boolean indicating whether the call was successful.
*/
function transferFrom(
EtherizedInterface owner,
address payable recipient,
uint256 amount
) external override returns (bool success) {
// Get the current allowance granted by the owner to the caller.
uint256 callerAllowance = _allowances[address(owner)][msg.sender];
// Block attempts to trigger calls when no allowance has been set.
require(callerAllowance != 0, "No allowance set for caller.");
// Reduce the allowance if it is not set to full allowance.
if (callerAllowance != uint256(-1)) {
require(callerAllowance >= amount, "Insufficient allowance.");
_approve(
address(owner), msg.sender, callerAllowance - amount
); // overflow safe (condition already checked).
}
// Trigger the call from the owner and revert if success is not returned.
success = owner.triggerEtherTransfer(recipient, amount);
require(success, "Owner did not confirm a successful transfer.");
// Emit an event with information regarding the triggered call.
emit Transfer(address(owner), recipient, amount);
}
/**
* @notice Approve `spender` to transfer up to `value` Ether on behalf of
* `msg.sender`.
* @param spender address The account to grant the allowance.
* @param value uint256 The size of the allowance to grant.
* @return success A boolean indicating whether the approval was successful.
*/
function approve(
address spender, uint256 value
) external override returns (bool success) {
_approve(msg.sender, spender, value);
success = true;
}
/**
* @notice Increase the current allowance of `spender` by `value` Ether.
* @param spender address The account to grant the additional allowance.
* @param addedValue uint256 The amount to increase the allowance by.
* @return success A boolean indicating whether the modification was
* successful.
*/
function increaseAllowance(
address spender, uint256 addedValue
) external override returns (bool success) {
_approve(
msg.sender, spender, _allowances[msg.sender][spender].add(addedValue)
);
success = true;
}
/**
* @notice Decrease the current allowance of `spender` by `value` Ether.
* @param spender address The account to decrease the allowance for.
* @param subtractedValue uint256 The amount to subtract from the allowance.
* @return success A boolean indicating whether the modification was
* successful.
*/
function decreaseAllowance(
address spender, uint256 subtractedValue
) external override returns (bool success) {
_approve(
msg.sender, spender, _allowances[msg.sender][spender].sub(subtractedValue)
);
success = true;
}
/**
* @notice Modify the current allowance of `spender` for `owner` by `value`
* Ether, increasing it if `increase` is true, otherwise decreasing it, via a
* meta-transaction that expires at `expiration` (or does not expire if the
* value is zero) and uses `salt` as an additional input, validated using
* `signatures`.
* @param owner address The account granting the modified allowance.
* @param spender address The account to modify the allowance for.
* @param value uint256 The amount to modify the allowance by.
* @param increase bool A flag that indicates whether the allowance will be
* increased by the specified value (if true) or decreased by it (if false).
* @param expiration uint256 A timestamp indicating how long the modification
* meta-transaction is valid for - a value of zero will signify no expiration.
* @param salt bytes32 An arbitrary salt to be provided as an additional input
* to the hash digest used to validate the signatures.
* @param signatures bytes A signature, or collection of signatures, that the
* owner must provide in order to authorize the meta-transaction. If the
* account of the owner does not have any runtime code deployed to it, the
* signature will be verified using ecrecover; otherwise, it will be supplied
* to the owner along with the message digest and context via ERC-1271 for
* validation.
* @return success A boolean indicating whether the modification was
* successful.
*/
function modifyAllowanceViaMetaTransaction(
address owner,
address spender,
uint256 value,
bool increase,
uint256 expiration,
bytes32 salt,
bytes calldata signatures
) external override returns (bool success) {
require(expiration == 0 || now <= expiration, "Meta-transaction expired.");
// Construct the meta-transaction's "context" information and validate it.
bytes memory context = abi.encodePacked(
address(this),
this.modifyAllowanceViaMetaTransaction.selector,
expiration,
salt,
abi.encode(owner, spender, value, increase)
);
_validateMetaTransaction(owner, context, signatures);
// Calculate new allowance by applying modification to current allowance.
uint256 currentAllowance = _allowances[owner][spender];
uint256 newAllowance = (
increase ? currentAllowance.add(value) : currentAllowance.sub(value)
);
// Modify the allowance.
_approve(owner, spender, newAllowance);
success = true;
}
/**
* @notice Cancel a specific meta-transaction for modifying an allowance. The
* designated owner or spender can both cancel the given meta-transaction.
* @param owner address The account granting the modified allowance.
* @param spender address The account to modify the allowance for.
* @param value uint256 The amount to modify the allowance by.
* @param increase bool A flag that indicates whether the allowance will be
* increased by the specified value (if true) or decreased by it (if false).
* @param expiration uint256 A timestamp indicating how long the modification
* meta-transaction is valid for - a value of zero will signify no expiration.
* @param salt bytes32 An arbitrary salt to be provided as an additional input
* to the hash digest used to validate the signatures.
* @return success A boolean indicating whether the cancellation was
* successful.
*/
function cancelAllowanceModificationMetaTransaction(
address owner,
address spender,
uint256 value,
bool increase,
uint256 expiration,
bytes32 salt
) external override returns (bool success) {
require(expiration == 0 || now <= expiration, "Meta-transaction expired.");
require(
msg.sender == owner || msg.sender == spender,
"Only owner or spender may cancel a given meta-transaction."
);
// Construct the meta-transaction's "context" information.
bytes memory context = abi.encodePacked(
address(this),
this.modifyAllowanceViaMetaTransaction.selector,
expiration,
salt,
abi.encode(owner, spender, value, increase)
);
// Construct the message hash using the provided context.
bytes32 messageHash = keccak256(context);
// Ensure message hash has not been used or cancelled and invalidate it.
require(
!_invalidMetaTxHashes[messageHash], "Meta-transaction already invalid."
);
_invalidMetaTxHashes[messageHash] = true;
success = true;
}
/**
* @notice View function to determine a meta-transaction message hash, and to
* determine if it is still valid (i.e. it has not yet been used and is not
* expired). The returned message hash will need to be prefixed using EIP-191
* 0x45 and hashed again in order to generate a final digest for the required
* signature - in other words, the same procedure utilized by `eth_Sign`.
* @param functionSelector bytes4 The function selector for the given
* meta-transaction. There is only one function selector available for V1:
* `0x2d657fa5` (the selector for `modifyAllowanceViaMetaTransaction`).
* @param arguments bytes The abi-encoded function arguments (aside from the
* `expiration`, `salt`, and `signatures` arguments) that should be supplied
* to the given function.
* @param expiration uint256 A timestamp indicating how long the given
* meta-transaction is valid for - a value of zero will signify no expiration.
* @param salt bytes32 An arbitrary salt to be provided as an additional input
* to the hash digest used to validate the signatures.
* @return messageHash The message hash corresponding to the meta-transaction.
*/
function getMetaTransactionMessageHash(
bytes4 functionSelector,
bytes calldata arguments,
uint256 expiration,
bytes32 salt
) external view override returns (bytes32 messageHash, bool valid) {
// Construct the meta-transaction's message hash based on relevant context.
messageHash = keccak256(
abi.encodePacked(
address(this), functionSelector, expiration, salt, arguments
)
);
// The meta-transaction is valid if it has not been used and is not expired.
valid = (
!_invalidMetaTxHashes[messageHash] && (expiration == 0 || now <= expiration)
);
}
/**
* @notice View function to get the total Ether balance of an account.
* @param account address The account to check the Ether balance for.
* @return amount The Ether balance of the given account.
*/
function balanceOf(
address account
) external view override returns (uint256 amount) {
amount = account.balance;
}
/**
* @notice View function to get the total allowance that `spender` has to
* transfer Ether from the `owner` account using `triggerCallFrom`.
* @param owner address The account that is granting the allowance.
* @param spender address The account that has been granted the allowance.
* @return etherAllowance The allowance of the given spender for the given
* owner.
*/
function allowance(
address owner, address spender
) external view override returns (uint256 etherAllowance) {
etherAllowance = _allowances[owner][spender];
}
/**
* @notice Private function to set the allowance for `spender` to transfer up
* to `value` tokens on behalf of `owner`.
* @param owner address The account that has granted the allowance.
* @param spender address The account to grant the allowance.
* @param value uint256 The size of the allowance to grant.
*/
function _approve(address owner, address spender, uint256 value) private {
require(owner != address(0), "ERC20: approve for the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = value;
emit Approval(owner, spender, value);
}
/**
* @notice Private function to enforce that a given Meta-transaction
* has not been used before and that the signature is valid according
* to the owner (using ERC-1271).
* @param owner address The account originating the meta-transaction.
* @param context bytes Information about the meta-transaction.
* @param signatures bytes Signature or signatures used to validate
* the meta-transaction.
*/
function _validateMetaTransaction(
address owner, bytes memory context, bytes memory signatures
) private {
// Construct the message hash using the provided context.
bytes32 messageHash = keccak256(context);
// Ensure message hash has not been used or cancelled and invalidate it.
require(
!_invalidMetaTxHashes[messageHash], "Meta-transaction no longer valid."
);
_invalidMetaTxHashes[messageHash] = true;
// Construct the digest to compare signatures against using EIP-191 0x45.
bytes32 digest = keccak256(
abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash)
);
// Validate via ERC-1271 against the owner account.
bytes memory data = abi.encode(digest, context);
bytes4 magic = ERC1271Interface(owner).isValidSignature(data, signatures);
require(magic == bytes4(0x20c13b0b), "Invalid signatures.");
}
}File 2 of 13: DharmaTradeReserve
pragma solidity 0.5.11;
/**
* @title DharmaTradeReserve
* @author 0age
* @notice This contract delegates all logic, including initialization, to an
* implementation contract specified by a hard-coded "upgrade beacon" contract.
*/
contract DharmaTradeReserve {
// Set upgrade beacon address as a constant (i.e. not in contract storage).
address private constant _UPGRADE_BEACON = address(
0x2Cf7C0333D9b7F94BbF55B9701227E359F92fD31
);
/**
* @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 3 of 13: WETH9
// Copyright (C) 2015, 2016, 2017 Dapphub
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
pragma solidity ^0.4.18;
contract WETH9 {
string public name = "Wrapped Ether";
string public symbol = "WETH";
uint8 public decimals = 18;
event Approval(address indexed src, address indexed guy, uint wad);
event Transfer(address indexed src, address indexed dst, uint wad);
event Deposit(address indexed dst, uint wad);
event Withdrawal(address indexed src, uint wad);
mapping (address => uint) public balanceOf;
mapping (address => mapping (address => uint)) public allowance;
function() public payable {
deposit();
}
function deposit() public payable {
balanceOf[msg.sender] += msg.value;
Deposit(msg.sender, msg.value);
}
function withdraw(uint wad) public {
require(balanceOf[msg.sender] >= wad);
balanceOf[msg.sender] -= wad;
msg.sender.transfer(wad);
Withdrawal(msg.sender, wad);
}
function totalSupply() public view returns (uint) {
return this.balance;
}
function approve(address guy, uint wad) public returns (bool) {
allowance[msg.sender][guy] = wad;
Approval(msg.sender, guy, wad);
return true;
}
function transfer(address dst, uint wad) public returns (bool) {
return transferFrom(msg.sender, dst, wad);
}
function transferFrom(address src, address dst, uint wad)
public
returns (bool)
{
require(balanceOf[src] >= wad);
if (src != msg.sender && allowance[src][msg.sender] != uint(-1)) {
require(allowance[src][msg.sender] >= wad);
allowance[src][msg.sender] -= wad;
}
balanceOf[src] -= wad;
balanceOf[dst] += wad;
Transfer(src, dst, wad);
return true;
}
}
/*
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
*/File 4 of 13: UniswapV2Router02
pragma solidity =0.6.6;
interface IUniswapV2Factory {
event PairCreated(address indexed token0, address indexed token1, address pair, uint);
function feeTo() external view returns (address);
function feeToSetter() external view returns (address);
function getPair(address tokenA, address tokenB) external view returns (address pair);
function allPairs(uint) external view returns (address pair);
function allPairsLength() external view returns (uint);
function createPair(address tokenA, address tokenB) external returns (address pair);
function setFeeTo(address) external;
function setFeeToSetter(address) external;
}
interface IUniswapV2Pair {
event Approval(address indexed owner, address indexed spender, uint value);
event Transfer(address indexed from, address indexed to, uint value);
function name() external pure returns (string memory);
function symbol() external pure returns (string memory);
function decimals() external pure returns (uint8);
function totalSupply() external view returns (uint);
function balanceOf(address owner) external view returns (uint);
function allowance(address owner, address spender) external view returns (uint);
function approve(address spender, uint value) external returns (bool);
function transfer(address to, uint value) external returns (bool);
function transferFrom(address from, address to, uint value) external returns (bool);
function DOMAIN_SEPARATOR() external view returns (bytes32);
function PERMIT_TYPEHASH() external pure returns (bytes32);
function nonces(address owner) external view returns (uint);
function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;
event Mint(address indexed sender, uint amount0, uint amount1);
event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
event Swap(
address indexed sender,
uint amount0In,
uint amount1In,
uint amount0Out,
uint amount1Out,
address indexed to
);
event Sync(uint112 reserve0, uint112 reserve1);
function MINIMUM_LIQUIDITY() external pure returns (uint);
function factory() external view returns (address);
function token0() external view returns (address);
function token1() external view returns (address);
function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
function price0CumulativeLast() external view returns (uint);
function price1CumulativeLast() external view returns (uint);
function kLast() external view returns (uint);
function mint(address to) external returns (uint liquidity);
function burn(address to) external returns (uint amount0, uint amount1);
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
function skim(address to) external;
function sync() external;
function initialize(address, address) external;
}
interface IUniswapV2Router01 {
function factory() external pure returns (address);
function WETH() external pure returns (address);
function addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external returns (uint amountA, uint amountB, uint liquidity);
function addLiquidityETH(
address token,
uint amountTokenDesired,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external payable returns (uint amountToken, uint amountETH, uint liquidity);
function removeLiquidity(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external returns (uint amountA, uint amountB);
function removeLiquidityETH(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external returns (uint amountToken, uint amountETH);
function removeLiquidityWithPermit(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external returns (uint amountA, uint amountB);
function removeLiquidityETHWithPermit(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external returns (uint amountToken, uint amountETH);
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
function swapTokensForExactTokens(
uint amountOut,
uint amountInMax,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
external
payable
returns (uint[] memory amounts);
function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
external
returns (uint[] memory amounts);
function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
external
returns (uint[] memory amounts);
function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
external
payable
returns (uint[] memory amounts);
function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB);
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut);
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn);
function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts);
}
interface IUniswapV2Router02 is IUniswapV2Router01 {
function removeLiquidityETHSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external returns (uint amountETH);
function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external returns (uint amountETH);
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external;
function swapExactETHForTokensSupportingFeeOnTransferTokens(
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external payable;
function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external;
}
interface IERC20 {
event Approval(address indexed owner, address indexed spender, uint value);
event Transfer(address indexed from, address indexed to, uint value);
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
function totalSupply() external view returns (uint);
function balanceOf(address owner) external view returns (uint);
function allowance(address owner, address spender) external view returns (uint);
function approve(address spender, uint value) external returns (bool);
function transfer(address to, uint value) external returns (bool);
function transferFrom(address from, address to, uint value) external returns (bool);
}
interface IWETH {
function deposit() external payable;
function transfer(address to, uint value) external returns (bool);
function withdraw(uint) external;
}
contract UniswapV2Router02 is IUniswapV2Router02 {
using SafeMath for uint;
address public immutable override factory;
address public immutable override WETH;
modifier ensure(uint deadline) {
require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED');
_;
}
constructor(address _factory, address _WETH) public {
factory = _factory;
WETH = _WETH;
}
receive() external payable {
assert(msg.sender == WETH); // only accept ETH via fallback from the WETH contract
}
// **** ADD LIQUIDITY ****
function _addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin
) internal virtual returns (uint amountA, uint amountB) {
// create the pair if it doesn't exist yet
if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {
IUniswapV2Factory(factory).createPair(tokenA, tokenB);
}
(uint reserveA, uint reserveB) = UniswapV2Library.getReserves(factory, tokenA, tokenB);
if (reserveA == 0 && reserveB == 0) {
(amountA, amountB) = (amountADesired, amountBDesired);
} else {
uint amountBOptimal = UniswapV2Library.quote(amountADesired, reserveA, reserveB);
if (amountBOptimal <= amountBDesired) {
require(amountBOptimal >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
(amountA, amountB) = (amountADesired, amountBOptimal);
} else {
uint amountAOptimal = UniswapV2Library.quote(amountBDesired, reserveB, reserveA);
assert(amountAOptimal <= amountADesired);
require(amountAOptimal >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
(amountA, amountB) = (amountAOptimal, amountBDesired);
}
}
}
function addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external virtual override ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) {
(amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin);
address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA);
TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB);
liquidity = IUniswapV2Pair(pair).mint(to);
}
function addLiquidityETH(
address token,
uint amountTokenDesired,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external virtual override payable ensure(deadline) returns (uint amountToken, uint amountETH, uint liquidity) {
(amountToken, amountETH) = _addLiquidity(
token,
WETH,
amountTokenDesired,
msg.value,
amountTokenMin,
amountETHMin
);
address pair = UniswapV2Library.pairFor(factory, token, WETH);
TransferHelper.safeTransferFrom(token, msg.sender, pair, amountToken);
IWETH(WETH).deposit{value: amountETH}();
assert(IWETH(WETH).transfer(pair, amountETH));
liquidity = IUniswapV2Pair(pair).mint(to);
// refund dust eth, if any
if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH);
}
// **** REMOVE LIQUIDITY ****
function removeLiquidity(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) public virtual override ensure(deadline) returns (uint amountA, uint amountB) {
address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
IUniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity); // send liquidity to pair
(uint amount0, uint amount1) = IUniswapV2Pair(pair).burn(to);
(address token0,) = UniswapV2Library.sortTokens(tokenA, tokenB);
(amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);
require(amountA >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
require(amountB >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
}
function removeLiquidityETH(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) public virtual override ensure(deadline) returns (uint amountToken, uint amountETH) {
(amountToken, amountETH) = removeLiquidity(
token,
WETH,
liquidity,
amountTokenMin,
amountETHMin,
address(this),
deadline
);
TransferHelper.safeTransfer(token, to, amountToken);
IWETH(WETH).withdraw(amountETH);
TransferHelper.safeTransferETH(to, amountETH);
}
function removeLiquidityWithPermit(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external virtual override returns (uint amountA, uint amountB) {
address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
uint value = approveMax ? uint(-1) : liquidity;
IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
(amountA, amountB) = removeLiquidity(tokenA, tokenB, liquidity, amountAMin, amountBMin, to, deadline);
}
function removeLiquidityETHWithPermit(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external virtual override returns (uint amountToken, uint amountETH) {
address pair = UniswapV2Library.pairFor(factory, token, WETH);
uint value = approveMax ? uint(-1) : liquidity;
IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
(amountToken, amountETH) = removeLiquidityETH(token, liquidity, amountTokenMin, amountETHMin, to, deadline);
}
// **** REMOVE LIQUIDITY (supporting fee-on-transfer tokens) ****
function removeLiquidityETHSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) public virtual override ensure(deadline) returns (uint amountETH) {
(, amountETH) = removeLiquidity(
token,
WETH,
liquidity,
amountTokenMin,
amountETHMin,
address(this),
deadline
);
TransferHelper.safeTransfer(token, to, IERC20(token).balanceOf(address(this)));
IWETH(WETH).withdraw(amountETH);
TransferHelper.safeTransferETH(to, amountETH);
}
function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external virtual override returns (uint amountETH) {
address pair = UniswapV2Library.pairFor(factory, token, WETH);
uint value = approveMax ? uint(-1) : liquidity;
IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
amountETH = removeLiquidityETHSupportingFeeOnTransferTokens(
token, liquidity, amountTokenMin, amountETHMin, to, deadline
);
}
// **** SWAP ****
// requires the initial amount to have already been sent to the first pair
function _swap(uint[] memory amounts, address[] memory path, address _to) internal virtual {
for (uint i; i < path.length - 1; i++) {
(address input, address output) = (path[i], path[i + 1]);
(address token0,) = UniswapV2Library.sortTokens(input, output);
uint amountOut = amounts[i + 1];
(uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));
address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(
amount0Out, amount1Out, to, new bytes(0)
);
}
}
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external virtual override ensure(deadline) returns (uint[] memory amounts) {
amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, to);
}
function swapTokensForExactTokens(
uint amountOut,
uint amountInMax,
address[] calldata path,
address to,
uint deadline
) external virtual override ensure(deadline) returns (uint[] memory amounts) {
amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, to);
}
function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
external
virtual
override
payable
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsOut(factory, msg.value, path);
require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
IWETH(WETH).deposit{value: amounts[0]}();
assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
_swap(amounts, path, to);
}
function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
external
virtual
override
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, address(this));
IWETH(WETH).withdraw(amounts[amounts.length - 1]);
TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
}
function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
external
virtual
override
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, address(this));
IWETH(WETH).withdraw(amounts[amounts.length - 1]);
TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
}
function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
external
virtual
override
payable
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
require(amounts[0] <= msg.value, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
IWETH(WETH).deposit{value: amounts[0]}();
assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
_swap(amounts, path, to);
// refund dust eth, if any
if (msg.value > amounts[0]) TransferHelper.safeTransferETH(msg.sender, msg.value - amounts[0]);
}
// **** SWAP (supporting fee-on-transfer tokens) ****
// requires the initial amount to have already been sent to the first pair
function _swapSupportingFeeOnTransferTokens(address[] memory path, address _to) internal virtual {
for (uint i; i < path.length - 1; i++) {
(address input, address output) = (path[i], path[i + 1]);
(address token0,) = UniswapV2Library.sortTokens(input, output);
IUniswapV2Pair pair = IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output));
uint amountInput;
uint amountOutput;
{ // scope to avoid stack too deep errors
(uint reserve0, uint reserve1,) = pair.getReserves();
(uint reserveInput, uint reserveOutput) = input == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
amountInput = IERC20(input).balanceOf(address(pair)).sub(reserveInput);
amountOutput = UniswapV2Library.getAmountOut(amountInput, reserveInput, reserveOutput);
}
(uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOutput) : (amountOutput, uint(0));
address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
pair.swap(amount0Out, amount1Out, to, new bytes(0));
}
}
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external virtual override ensure(deadline) {
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn
);
uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);
_swapSupportingFeeOnTransferTokens(path, to);
require(
IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,
'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'
);
}
function swapExactETHForTokensSupportingFeeOnTransferTokens(
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
)
external
virtual
override
payable
ensure(deadline)
{
require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
uint amountIn = msg.value;
IWETH(WETH).deposit{value: amountIn}();
assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn));
uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);
_swapSupportingFeeOnTransferTokens(path, to);
require(
IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,
'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'
);
}
function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
)
external
virtual
override
ensure(deadline)
{
require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn
);
_swapSupportingFeeOnTransferTokens(path, address(this));
uint amountOut = IERC20(WETH).balanceOf(address(this));
require(amountOut >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
IWETH(WETH).withdraw(amountOut);
TransferHelper.safeTransferETH(to, amountOut);
}
// **** LIBRARY FUNCTIONS ****
function quote(uint amountA, uint reserveA, uint reserveB) public pure virtual override returns (uint amountB) {
return UniswapV2Library.quote(amountA, reserveA, reserveB);
}
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut)
public
pure
virtual
override
returns (uint amountOut)
{
return UniswapV2Library.getAmountOut(amountIn, reserveIn, reserveOut);
}
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut)
public
pure
virtual
override
returns (uint amountIn)
{
return UniswapV2Library.getAmountIn(amountOut, reserveIn, reserveOut);
}
function getAmountsOut(uint amountIn, address[] memory path)
public
view
virtual
override
returns (uint[] memory amounts)
{
return UniswapV2Library.getAmountsOut(factory, amountIn, path);
}
function getAmountsIn(uint amountOut, address[] memory path)
public
view
virtual
override
returns (uint[] memory amounts)
{
return UniswapV2Library.getAmountsIn(factory, amountOut, path);
}
}
// a library for performing overflow-safe math, courtesy of DappHub (https://github.com/dapphub/ds-math)
library SafeMath {
function add(uint x, uint y) internal pure returns (uint z) {
require((z = x + y) >= x, 'ds-math-add-overflow');
}
function sub(uint x, uint y) internal pure returns (uint z) {
require((z = x - y) <= x, 'ds-math-sub-underflow');
}
function mul(uint x, uint y) internal pure returns (uint z) {
require(y == 0 || (z = x * y) / y == x, 'ds-math-mul-overflow');
}
}
library UniswapV2Library {
using SafeMath for uint;
// returns sorted token addresses, used to handle return values from pairs sorted in this order
function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) {
require(tokenA != tokenB, 'UniswapV2Library: IDENTICAL_ADDRESSES');
(token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
require(token0 != address(0), 'UniswapV2Library: ZERO_ADDRESS');
}
// calculates the CREATE2 address for a pair without making any external calls
function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) {
(address token0, address token1) = sortTokens(tokenA, tokenB);
pair = address(uint(keccak256(abi.encodePacked(
hex'ff',
factory,
keccak256(abi.encodePacked(token0, token1)),
hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init code hash
))));
}
// fetches and sorts the reserves for a pair
function getReserves(address factory, address tokenA, address tokenB) internal view returns (uint reserveA, uint reserveB) {
(address token0,) = sortTokens(tokenA, tokenB);
(uint reserve0, uint reserve1,) = IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves();
(reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
}
// given some amount of an asset and pair reserves, returns an equivalent amount of the other asset
function quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) {
require(amountA > 0, 'UniswapV2Library: INSUFFICIENT_AMOUNT');
require(reserveA > 0 && reserveB > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
amountB = amountA.mul(reserveB) / reserveA;
}
// given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {
require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT');
require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
uint amountInWithFee = amountIn.mul(997);
uint numerator = amountInWithFee.mul(reserveOut);
uint denominator = reserveIn.mul(1000).add(amountInWithFee);
amountOut = numerator / denominator;
}
// given an output amount of an asset and pair reserves, returns a required input amount of the other asset
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn) {
require(amountOut > 0, 'UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT');
require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
uint numerator = reserveIn.mul(amountOut).mul(1000);
uint denominator = reserveOut.sub(amountOut).mul(997);
amountIn = (numerator / denominator).add(1);
}
// performs chained getAmountOut calculations on any number of pairs
function getAmountsOut(address factory, uint amountIn, address[] memory path) internal view returns (uint[] memory amounts) {
require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');
amounts = new uint[](path.length);
amounts[0] = amountIn;
for (uint i; i < path.length - 1; i++) {
(uint reserveIn, uint reserveOut) = getReserves(factory, path[i], path[i + 1]);
amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut);
}
}
// performs chained getAmountIn calculations on any number of pairs
function getAmountsIn(address factory, uint amountOut, address[] memory path) internal view returns (uint[] memory amounts) {
require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');
amounts = new uint[](path.length);
amounts[amounts.length - 1] = amountOut;
for (uint i = path.length - 1; i > 0; i--) {
(uint reserveIn, uint reserveOut) = getReserves(factory, path[i - 1], path[i]);
amounts[i - 1] = getAmountIn(amounts[i], reserveIn, reserveOut);
}
}
}
// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false
library TransferHelper {
function safeApprove(address token, address to, uint value) internal {
// bytes4(keccak256(bytes('approve(address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: APPROVE_FAILED');
}
function safeTransfer(address token, address to, uint value) internal {
// bytes4(keccak256(bytes('transfer(address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FAILED');
}
function safeTransferFrom(address token, address from, address to, uint value) internal {
// bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FROM_FAILED');
}
function safeTransferETH(address to, uint value) internal {
(bool success,) = to.call{value:value}(new bytes(0));
require(success, 'TransferHelper: ETH_TRANSFER_FAILED');
}
}File 5 of 13: UniswapV2Pair
// File: contracts/interfaces/IUniswapV2Pair.sol
pragma solidity >=0.5.0;
interface IUniswapV2Pair {
event Approval(address indexed owner, address indexed spender, uint value);
event Transfer(address indexed from, address indexed to, uint value);
function name() external pure returns (string memory);
function symbol() external pure returns (string memory);
function decimals() external pure returns (uint8);
function totalSupply() external view returns (uint);
function balanceOf(address owner) external view returns (uint);
function allowance(address owner, address spender) external view returns (uint);
function approve(address spender, uint value) external returns (bool);
function transfer(address to, uint value) external returns (bool);
function transferFrom(address from, address to, uint value) external returns (bool);
function DOMAIN_SEPARATOR() external view returns (bytes32);
function PERMIT_TYPEHASH() external pure returns (bytes32);
function nonces(address owner) external view returns (uint);
function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;
event Mint(address indexed sender, uint amount0, uint amount1);
event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
event Swap(
address indexed sender,
uint amount0In,
uint amount1In,
uint amount0Out,
uint amount1Out,
address indexed to
);
event Sync(uint112 reserve0, uint112 reserve1);
function MINIMUM_LIQUIDITY() external pure returns (uint);
function factory() external view returns (address);
function token0() external view returns (address);
function token1() external view returns (address);
function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
function price0CumulativeLast() external view returns (uint);
function price1CumulativeLast() external view returns (uint);
function kLast() external view returns (uint);
function mint(address to) external returns (uint liquidity);
function burn(address to) external returns (uint amount0, uint amount1);
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
function skim(address to) external;
function sync() external;
function initialize(address, address) external;
}
// File: contracts/interfaces/IUniswapV2ERC20.sol
pragma solidity >=0.5.0;
interface IUniswapV2ERC20 {
event Approval(address indexed owner, address indexed spender, uint value);
event Transfer(address indexed from, address indexed to, uint value);
function name() external pure returns (string memory);
function symbol() external pure returns (string memory);
function decimals() external pure returns (uint8);
function totalSupply() external view returns (uint);
function balanceOf(address owner) external view returns (uint);
function allowance(address owner, address spender) external view returns (uint);
function approve(address spender, uint value) external returns (bool);
function transfer(address to, uint value) external returns (bool);
function transferFrom(address from, address to, uint value) external returns (bool);
function DOMAIN_SEPARATOR() external view returns (bytes32);
function PERMIT_TYPEHASH() external pure returns (bytes32);
function nonces(address owner) external view returns (uint);
function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;
}
// File: contracts/libraries/SafeMath.sol
pragma solidity =0.5.16;
// a library for performing overflow-safe math, courtesy of DappHub (https://github.com/dapphub/ds-math)
library SafeMath {
function add(uint x, uint y) internal pure returns (uint z) {
require((z = x + y) >= x, 'ds-math-add-overflow');
}
function sub(uint x, uint y) internal pure returns (uint z) {
require((z = x - y) <= x, 'ds-math-sub-underflow');
}
function mul(uint x, uint y) internal pure returns (uint z) {
require(y == 0 || (z = x * y) / y == x, 'ds-math-mul-overflow');
}
}
// File: contracts/UniswapV2ERC20.sol
pragma solidity =0.5.16;
contract UniswapV2ERC20 is IUniswapV2ERC20 {
using SafeMath for uint;
string public constant name = 'Uniswap V2';
string public constant symbol = 'UNI-V2';
uint8 public constant decimals = 18;
uint public totalSupply;
mapping(address => uint) public balanceOf;
mapping(address => mapping(address => uint)) public allowance;
bytes32 public DOMAIN_SEPARATOR;
// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
mapping(address => uint) public nonces;
event Approval(address indexed owner, address indexed spender, uint value);
event Transfer(address indexed from, address indexed to, uint value);
constructor() public {
uint chainId;
assembly {
chainId := chainid
}
DOMAIN_SEPARATOR = keccak256(
abi.encode(
keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
keccak256(bytes(name)),
keccak256(bytes('1')),
chainId,
address(this)
)
);
}
function _mint(address to, uint value) internal {
totalSupply = totalSupply.add(value);
balanceOf[to] = balanceOf[to].add(value);
emit Transfer(address(0), to, value);
}
function _burn(address from, uint value) internal {
balanceOf[from] = balanceOf[from].sub(value);
totalSupply = totalSupply.sub(value);
emit Transfer(from, address(0), value);
}
function _approve(address owner, address spender, uint value) private {
allowance[owner][spender] = value;
emit Approval(owner, spender, value);
}
function _transfer(address from, address to, uint value) private {
balanceOf[from] = balanceOf[from].sub(value);
balanceOf[to] = balanceOf[to].add(value);
emit Transfer(from, to, value);
}
function approve(address spender, uint value) external returns (bool) {
_approve(msg.sender, spender, value);
return true;
}
function transfer(address to, uint value) external returns (bool) {
_transfer(msg.sender, to, value);
return true;
}
function transferFrom(address from, address to, uint value) external returns (bool) {
if (allowance[from][msg.sender] != uint(-1)) {
allowance[from][msg.sender] = allowance[from][msg.sender].sub(value);
}
_transfer(from, to, value);
return true;
}
function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {
require(deadline >= block.timestamp, 'UniswapV2: EXPIRED');
bytes32 digest = keccak256(
abi.encodePacked(
'\x19\x01',
DOMAIN_SEPARATOR,
keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
)
);
address recoveredAddress = ecrecover(digest, v, r, s);
require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE');
_approve(owner, spender, value);
}
}
// File: contracts/libraries/Math.sol
pragma solidity =0.5.16;
// a library for performing various math operations
library Math {
function min(uint x, uint y) internal pure returns (uint z) {
z = x < y ? x : y;
}
// babylonian method (https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method)
function sqrt(uint y) internal pure returns (uint z) {
if (y > 3) {
z = y;
uint x = y / 2 + 1;
while (x < z) {
z = x;
x = (y / x + x) / 2;
}
} else if (y != 0) {
z = 1;
}
}
}
// File: contracts/libraries/UQ112x112.sol
pragma solidity =0.5.16;
// a library for handling binary fixed point numbers (https://en.wikipedia.org/wiki/Q_(number_format))
// range: [0, 2**112 - 1]
// resolution: 1 / 2**112
library UQ112x112 {
uint224 constant Q112 = 2**112;
// encode a uint112 as a UQ112x112
function encode(uint112 y) internal pure returns (uint224 z) {
z = uint224(y) * Q112; // never overflows
}
// divide a UQ112x112 by a uint112, returning a UQ112x112
function uqdiv(uint224 x, uint112 y) internal pure returns (uint224 z) {
z = x / uint224(y);
}
}
// File: contracts/interfaces/IERC20.sol
pragma solidity >=0.5.0;
interface IERC20 {
event Approval(address indexed owner, address indexed spender, uint value);
event Transfer(address indexed from, address indexed to, uint value);
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
function totalSupply() external view returns (uint);
function balanceOf(address owner) external view returns (uint);
function allowance(address owner, address spender) external view returns (uint);
function approve(address spender, uint value) external returns (bool);
function transfer(address to, uint value) external returns (bool);
function transferFrom(address from, address to, uint value) external returns (bool);
}
// File: contracts/interfaces/IUniswapV2Factory.sol
pragma solidity >=0.5.0;
interface IUniswapV2Factory {
event PairCreated(address indexed token0, address indexed token1, address pair, uint);
function feeTo() external view returns (address);
function feeToSetter() external view returns (address);
function getPair(address tokenA, address tokenB) external view returns (address pair);
function allPairs(uint) external view returns (address pair);
function allPairsLength() external view returns (uint);
function createPair(address tokenA, address tokenB) external returns (address pair);
function setFeeTo(address) external;
function setFeeToSetter(address) external;
}
// File: contracts/interfaces/IUniswapV2Callee.sol
pragma solidity >=0.5.0;
interface IUniswapV2Callee {
function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external;
}
// File: contracts/UniswapV2Pair.sol
pragma solidity =0.5.16;
contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 {
using SafeMath for uint;
using UQ112x112 for uint224;
uint public constant MINIMUM_LIQUIDITY = 10**3;
bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)')));
address public factory;
address public token0;
address public token1;
uint112 private reserve0; // uses single storage slot, accessible via getReserves
uint112 private reserve1; // uses single storage slot, accessible via getReserves
uint32 private blockTimestampLast; // uses single storage slot, accessible via getReserves
uint public price0CumulativeLast;
uint public price1CumulativeLast;
uint public kLast; // reserve0 * reserve1, as of immediately after the most recent liquidity event
uint private unlocked = 1;
modifier lock() {
require(unlocked == 1, 'UniswapV2: LOCKED');
unlocked = 0;
_;
unlocked = 1;
}
function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) {
_reserve0 = reserve0;
_reserve1 = reserve1;
_blockTimestampLast = blockTimestampLast;
}
function _safeTransfer(address token, address to, uint value) private {
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), 'UniswapV2: TRANSFER_FAILED');
}
event Mint(address indexed sender, uint amount0, uint amount1);
event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
event Swap(
address indexed sender,
uint amount0In,
uint amount1In,
uint amount0Out,
uint amount1Out,
address indexed to
);
event Sync(uint112 reserve0, uint112 reserve1);
constructor() public {
factory = msg.sender;
}
// called once by the factory at time of deployment
function initialize(address _token0, address _token1) external {
require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check
token0 = _token0;
token1 = _token1;
}
// update reserves and, on the first call per block, price accumulators
function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {
require(balance0 <= uint112(-1) && balance1 <= uint112(-1), 'UniswapV2: OVERFLOW');
uint32 blockTimestamp = uint32(block.timestamp % 2**32);
uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired
if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {
// * never overflows, and + overflow is desired
price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
}
reserve0 = uint112(balance0);
reserve1 = uint112(balance1);
blockTimestampLast = blockTimestamp;
emit Sync(reserve0, reserve1);
}
// if fee is on, mint liquidity equivalent to 1/6th of the growth in sqrt(k)
function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) {
address feeTo = IUniswapV2Factory(factory).feeTo();
feeOn = feeTo != address(0);
uint _kLast = kLast; // gas savings
if (feeOn) {
if (_kLast != 0) {
uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1));
uint rootKLast = Math.sqrt(_kLast);
if (rootK > rootKLast) {
uint numerator = totalSupply.mul(rootK.sub(rootKLast));
uint denominator = rootK.mul(5).add(rootKLast);
uint liquidity = numerator / denominator;
if (liquidity > 0) _mint(feeTo, liquidity);
}
}
} else if (_kLast != 0) {
kLast = 0;
}
}
// this low-level function should be called from a contract which performs important safety checks
function mint(address to) external lock returns (uint liquidity) {
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
uint balance0 = IERC20(token0).balanceOf(address(this));
uint balance1 = IERC20(token1).balanceOf(address(this));
uint amount0 = balance0.sub(_reserve0);
uint amount1 = balance1.sub(_reserve1);
bool feeOn = _mintFee(_reserve0, _reserve1);
uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
if (_totalSupply == 0) {
liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
_mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens
} else {
liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);
}
require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');
_mint(to, liquidity);
_update(balance0, balance1, _reserve0, _reserve1);
if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
emit Mint(msg.sender, amount0, amount1);
}
// this low-level function should be called from a contract which performs important safety checks
function burn(address to) external lock returns (uint amount0, uint amount1) {
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
address _token0 = token0; // gas savings
address _token1 = token1; // gas savings
uint balance0 = IERC20(_token0).balanceOf(address(this));
uint balance1 = IERC20(_token1).balanceOf(address(this));
uint liquidity = balanceOf[address(this)];
bool feeOn = _mintFee(_reserve0, _reserve1);
uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution
amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution
require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED');
_burn(address(this), liquidity);
_safeTransfer(_token0, to, amount0);
_safeTransfer(_token1, to, amount1);
balance0 = IERC20(_token0).balanceOf(address(this));
balance1 = IERC20(_token1).balanceOf(address(this));
_update(balance0, balance1, _reserve0, _reserve1);
if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
emit Burn(msg.sender, amount0, amount1, to);
}
// this low-level function should be called from a contract which performs important safety checks
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');
uint balance0;
uint balance1;
{ // scope for _token{0,1}, avoids stack too deep errors
address _token0 = token0;
address _token1 = token1;
require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens
if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
balance0 = IERC20(_token0).balanceOf(address(this));
balance1 = IERC20(_token1).balanceOf(address(this));
}
uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');
{ // scope for reserve{0,1}Adjusted, avoids stack too deep errors
uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');
}
_update(balance0, balance1, _reserve0, _reserve1);
emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
}
// force balances to match reserves
function skim(address to) external lock {
address _token0 = token0; // gas savings
address _token1 = token1; // gas savings
_safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0));
_safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1));
}
// force reserves to match balances
function sync() external lock {
_update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);
}
}File 6 of 13: OrionToken
{"Context.sol":{"content":"pragma solidity ^0.5.0;\n\n/*\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with GSN meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\ncontract Context {\n // Empty internal constructor, to prevent people from mistakenly deploying\n // an instance of this contract, which should be used via inheritance.\n constructor () internal { }\n // solhint-disable-previous-line no-empty-blocks\n\n function _msgSender() internal view returns (address payable) {\n return msg.sender;\n }\n\n function _msgData() internal view returns (bytes memory) {\n this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691\n return msg.data;\n }\n}\n"},"ERC20.sol":{"content":"pragma solidity ^0.5.0;\n\nimport \"./Context.sol\";\nimport \"./IERC20.sol\";\nimport \"./SafeMath.sol\";\n\n/**\n * @dev Implementation of the {IERC20} interface.\n *\n * This implementation is agnostic to the way tokens are created. This means\n * that a supply mechanism has to be added in a derived contract using {_mint}.\n * For a generic mechanism see {ERC20Mintable}.\n *\n * TIP: For a detailed writeup see our guide\n * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How\n * to implement supply mechanisms].\n *\n * We have followed general OpenZeppelin guidelines: functions revert instead\n * of returning `false` on failure. This behavior is nonetheless conventional\n * and does not conflict with the expectations of ERC20 applications.\n *\n * Additionally, an {Approval} event is emitted on calls to {transferFrom}.\n * This allows applications to reconstruct the allowance for all accounts just\n * by listening to said events. Other implementations of the EIP may not emit\n * these events, as it isn\u0027t required by the specification.\n *\n * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}\n * functions have been added to mitigate the well-known issues around setting\n * allowances. See {IERC20-approve}.\n */\ncontract ERC20 is Context, IERC20 {\n using SafeMath for uint256;\n\n mapping (address =\u003e uint256) private _balances;\n\n mapping (address =\u003e mapping (address =\u003e uint256)) private _allowances;\n\n uint256 private _totalSupply;\n\n /**\n * @dev See {IERC20-totalSupply}.\n */\n function totalSupply() public view returns (uint256) {\n return _totalSupply;\n }\n\n /**\n * @dev See {IERC20-balanceOf}.\n */\n function balanceOf(address account) public view returns (uint256) {\n return _balances[account];\n }\n\n /**\n * @dev See {IERC20-transfer}.\n *\n * Requirements:\n *\n * - `recipient` cannot be the zero address.\n * - the caller must have a balance of at least `amount`.\n */\n function transfer(address recipient, uint256 amount) public returns (bool) {\n _transfer(_msgSender(), recipient, amount);\n return true;\n }\n\n /**\n * @dev See {IERC20-allowance}.\n */\n function allowance(address owner, address spender) public view returns (uint256) {\n return _allowances[owner][spender];\n }\n\n /**\n * @dev See {IERC20-approve}.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function approve(address spender, uint256 amount) public returns (bool) {\n _approve(_msgSender(), spender, amount);\n return true;\n }\n\n /**\n * @dev See {IERC20-transferFrom}.\n *\n * Emits an {Approval} event indicating the updated allowance. This is not\n * required by the EIP. See the note at the beginning of {ERC20};\n *\n * Requirements:\n * - `sender` and `recipient` cannot be the zero address.\n * - `sender` must have a balance of at least `amount`.\n * - the caller must have allowance for `sender`\u0027s tokens of at least\n * `amount`.\n */\n function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {\n _transfer(sender, recipient, amount);\n _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, \"ERC20: transfer amount exceeds allowance\"));\n return true;\n }\n\n /**\n * @dev Atomically increases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {\n _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));\n return true;\n }\n\n /**\n * @dev Atomically decreases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n * - `spender` must have allowance for the caller of at least\n * `subtractedValue`.\n */\n function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {\n _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, \"ERC20: decreased allowance below zero\"));\n return true;\n }\n\n /**\n * @dev Moves tokens `amount` from `sender` to `recipient`.\n *\n * This is internal function is equivalent to {transfer}, and can be used to\n * e.g. implement automatic token fees, slashing mechanisms, etc.\n *\n * Emits a {Transfer} event.\n *\n * Requirements:\n *\n * - `sender` cannot be the zero address.\n * - `recipient` cannot be the zero address.\n * - `sender` must have a balance of at least `amount`.\n */\n function _transfer(address sender, address recipient, uint256 amount) internal {\n require(sender != address(0), \"ERC20: transfer from the zero address\");\n require(recipient != address(0), \"ERC20: transfer to the zero address\");\n\n _balances[sender] = _balances[sender].sub(amount, \"ERC20: transfer amount exceeds balance\");\n _balances[recipient] = _balances[recipient].add(amount);\n emit Transfer(sender, recipient, amount);\n }\n\n /** @dev Creates `amount` tokens and assigns them to `account`, increasing\n * the total supply.\n *\n * Emits a {Transfer} event with `from` set to the zero address.\n *\n * Requirements\n *\n * - `to` cannot be the zero address.\n */\n function _mint(address account, uint256 amount) internal {\n require(account != address(0), \"ERC20: mint to the zero address\");\n\n _totalSupply = _totalSupply.add(amount);\n _balances[account] = _balances[account].add(amount);\n emit Transfer(address(0), account, amount);\n }\n\n /**\n * @dev Destroys `amount` tokens from `account`, reducing the\n * total supply.\n *\n * Emits a {Transfer} event with `to` set to the zero address.\n *\n * Requirements\n *\n * - `account` cannot be the zero address.\n * - `account` must have at least `amount` tokens.\n */\n function _burn(address account, uint256 amount) internal {\n require(account != address(0), \"ERC20: burn from the zero address\");\n\n _balances[account] = _balances[account].sub(amount, \"ERC20: burn amount exceeds balance\");\n _totalSupply = _totalSupply.sub(amount);\n emit Transfer(account, address(0), amount);\n }\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.\n *\n * This is internal function is equivalent to `approve`, and can be used to\n * e.g. set automatic allowances for certain subsystems, etc.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `owner` cannot be the zero address.\n * - `spender` cannot be the zero address.\n */\n function _approve(address owner, address spender, uint256 amount) internal {\n require(owner != address(0), \"ERC20: approve from the zero address\");\n require(spender != address(0), \"ERC20: approve to the zero address\");\n\n _allowances[owner][spender] = amount;\n emit Approval(owner, spender, amount);\n }\n\n /**\n * @dev Destroys `amount` tokens from `account`.`amount` is then deducted\n * from the caller\u0027s allowance.\n *\n * See {_burn} and {_approve}.\n */\n function _burnFrom(address account, uint256 amount) internal {\n _burn(account, amount);\n _approve(account, _msgSender(), _allowances[account][_msgSender()].sub(amount, \"ERC20: burn amount exceeds allowance\"));\n }\n}\n"},"ERC20Capped.sol":{"content":"pragma solidity ^0.5.0;\n\nimport \"./ERC20Mintable.sol\";\n\n/**\n * @dev Extension of {ERC20Mintable} that adds a cap to the supply of tokens.\n */\ncontract ERC20Capped is ERC20Mintable {\n uint256 private _cap;\n\n /**\n * @dev Sets the value of the `cap`. This value is immutable, it can only be\n * set once during construction.\n */\n constructor (uint256 cap) public {\n require(cap \u003e 0, \"ERC20Capped: cap is 0\");\n _cap = cap;\n }\n\n /**\n * @dev Returns the cap on the token\u0027s total supply.\n */\n function cap() public view returns (uint256) {\n return _cap;\n }\n\n /**\n * @dev See {ERC20Mintable-mint}.\n *\n * Requirements:\n *\n * - `value` must not cause the total supply to go over the cap.\n */\n function _mint(address account, uint256 value) internal {\n require(totalSupply().add(value) \u003c= _cap, \"ERC20Capped: cap exceeded\");\n super._mint(account, value);\n }\n}\n"},"ERC20Detailed.sol":{"content":"pragma solidity ^0.5.0;\n\nimport \"./IERC20.sol\";\n\n/**\n * @dev Optional functions from the ERC20 standard.\n */\ncontract ERC20Detailed is IERC20 {\n string private _name;\n string private _symbol;\n uint8 private _decimals;\n\n /**\n * @dev Sets the values for `name`, `symbol`, and `decimals`. All three of\n * these values are immutable: they can only be set once during\n * construction.\n */\n constructor (string memory name, string memory symbol, uint8 decimals) public {\n _name = name;\n _symbol = symbol;\n _decimals = decimals;\n }\n\n /**\n * @dev Returns the name of the token.\n */\n function name() public view returns (string memory) {\n return _name;\n }\n\n /**\n * @dev Returns the symbol of the token, usually a shorter version of the\n * name.\n */\n function symbol() public view returns (string memory) {\n return _symbol;\n }\n\n /**\n * @dev Returns the number of decimals used to get its user representation.\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\n * be displayed to a user as `5,05` (`505 / 10 ** 2`).\n *\n * Tokens usually opt for a value of 18, imitating the relationship between\n * Ether and Wei.\n *\n * NOTE: This information is only used for _display_ purposes: it in\n * no way affects any of the arithmetic of the contract, including\n * {IERC20-balanceOf} and {IERC20-transfer}.\n */\n function decimals() public view returns (uint8) {\n return _decimals;\n }\n}\n"},"ERC20Mintable.sol":{"content":"pragma solidity ^0.5.0;\n\nimport \"./ERC20.sol\";\nimport \"./MinterRole.sol\";\n\n/**\n * @dev Extension of {ERC20} that adds a set of accounts with the {MinterRole},\n * which have permission to mint (create) new tokens as they see fit.\n *\n * At construction, the deployer of the contract is the only minter.\n */\ncontract ERC20Mintable is ERC20, MinterRole {\n /**\n * @dev See {ERC20-_mint}.\n *\n * Requirements:\n *\n * - the caller must have the {MinterRole}.\n */\n function mint(address account, uint256 amount) public onlyMinter returns (bool) {\n _mint(account, amount);\n return true;\n }\n}\n"},"IERC20.sol":{"content":"pragma solidity ^0.5.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP. Does not include\n * the optional functions; to access them see {ERC20Detailed}.\n */\ninterface IERC20 {\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller\u0027s account to `recipient`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address recipient, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller\u0027s tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender\u0027s allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `sender` to `recipient` using the\n * allowance mechanism. `amount` is then deducted from the caller\u0027s\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);\n\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n}\n"},"Math.sol":{"content":"pragma solidity ^0.5.0;\n\n/**\n * @dev Standard math utilities missing in the Solidity language.\n */\nlibrary Math {\n /**\n * @dev Returns the largest of two numbers.\n */\n function max(uint256 a, uint256 b) internal pure returns (uint256) {\n return a \u003e= b ? a : b;\n }\n\n /**\n * @dev Returns the smallest of two numbers.\n */\n function min(uint256 a, uint256 b) internal pure returns (uint256) {\n return a \u003c b ? a : b;\n }\n\n /**\n * @dev Returns the average of two numbers. The result is rounded towards\n * zero.\n */\n function average(uint256 a, uint256 b) internal pure returns (uint256) {\n // (a + b) / 2 can overflow, so we distribute\n return (a / 2) + (b / 2) + ((a % 2 + b % 2) / 2);\n }\n}\n"},"MinterRole.sol":{"content":"pragma solidity ^0.5.0;\n\nimport \"./Context.sol\";\nimport \"./Roles.sol\";\n\ncontract MinterRole is Context {\n using Roles for Roles.Role;\n\n event MinterAdded(address indexed account);\n event MinterRemoved(address indexed account);\n\n Roles.Role private _minters;\n\n constructor () internal {\n _addMinter(_msgSender());\n }\n\n modifier onlyMinter() {\n require(isMinter(_msgSender()), \"MinterRole: caller does not have the Minter role\");\n _;\n }\n\n function isMinter(address account) public view returns (bool) {\n return _minters.has(account);\n }\n\n function addMinter(address account) public onlyMinter {\n _addMinter(account);\n }\n\n function renounceMinter() public {\n _removeMinter(_msgSender());\n }\n\n function _addMinter(address account) internal {\n _minters.add(account);\n emit MinterAdded(account);\n }\n\n function _removeMinter(address account) internal {\n _minters.remove(account);\n emit MinterRemoved(account);\n }\n}\n"},"OrionToken.sol":{"content":"pragma solidity 0.5.10;\n\nimport \"./ERC20Detailed.sol\";\nimport \"./ERC20Capped.sol\";\n\n/**\n * @title ORN Token Contract\n *\n * @author Orion Protocol\n *\n * Address:\n * Name: Orion Protocol\n * Symbol: ORN\n * Decimals: 8\n * Initial Supply: 0\n * Max Supply: 100,000,000.00000000\n * Features: Capped, Mintable\n * Minters:\n *\n */\ncontract OrionToken is ERC20Detailed, ERC20Capped {\n constructor()\n public\n ERC20Detailed(\"Orion Protocol\", \"ORN\", 8)\n ERC20Capped(100e6 * 1e8)\n {}\n}\n"},"Roles.sol":{"content":"pragma solidity ^0.5.0;\n\n/**\n * @title Roles\n * @dev Library for managing addresses assigned to a Role.\n */\nlibrary Roles {\n struct Role {\n mapping (address =\u003e bool) bearer;\n }\n\n /**\n * @dev Give an account access to this role.\n */\n function add(Role storage role, address account) internal {\n require(!has(role, account), \"Roles: account already has role\");\n role.bearer[account] = true;\n }\n\n /**\n * @dev Remove an account\u0027s access to this role.\n */\n function remove(Role storage role, address account) internal {\n require(has(role, account), \"Roles: account does not have role\");\n role.bearer[account] = false;\n }\n\n /**\n * @dev Check if an account has this role.\n * @return bool\n */\n function has(Role storage role, address account) internal view returns (bool) {\n require(account != address(0), \"Roles: account is the zero address\");\n return role.bearer[account];\n }\n}\n"},"SafeMath.sol":{"content":"pragma solidity ^0.5.0;\n\n/**\n * @dev Wrappers over Solidity\u0027s arithmetic operations with added overflow\n * checks.\n *\n * Arithmetic operations in Solidity wrap on overflow. This can easily result\n * in bugs, because programmers usually assume that an overflow raises an\n * error, which is the standard behavior in high level programming languages.\n * `SafeMath` restores this intuition by reverting the transaction when an\n * operation overflows.\n *\n * Using this library instead of the unchecked operations eliminates an entire\n * class of bugs, so it\u0027s recommended to use it always.\n */\nlibrary SafeMath {\n /**\n * @dev Returns the addition of two unsigned integers, reverting on\n * overflow.\n *\n * Counterpart to Solidity\u0027s `+` operator.\n *\n * Requirements:\n * - Addition cannot overflow.\n */\n function add(uint256 a, uint256 b) internal pure returns (uint256) {\n uint256 c = a + b;\n require(c \u003e= a, \"SafeMath: addition overflow\");\n\n return c;\n }\n\n /**\n * @dev Returns the subtraction of two unsigned integers, reverting on\n * overflow (when the result is negative).\n *\n * Counterpart to Solidity\u0027s `-` operator.\n *\n * Requirements:\n * - Subtraction cannot overflow.\n */\n function sub(uint256 a, uint256 b) internal pure returns (uint256) {\n return sub(a, b, \"SafeMath: subtraction overflow\");\n }\n\n /**\n * @dev Returns the subtraction of two unsigned integers, reverting with custom message on\n * overflow (when the result is negative).\n *\n * Counterpart to Solidity\u0027s `-` operator.\n *\n * Requirements:\n * - Subtraction cannot overflow.\n *\n * _Available since v2.4.0._\n */\n function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {\n require(b \u003c= a, errorMessage);\n uint256 c = a - b;\n\n return c;\n }\n\n /**\n * @dev Returns the multiplication of two unsigned integers, reverting on\n * overflow.\n *\n * Counterpart to Solidity\u0027s `*` operator.\n *\n * Requirements:\n * - Multiplication cannot overflow.\n */\n function mul(uint256 a, uint256 b) internal pure returns (uint256) {\n // Gas optimization: this is cheaper than requiring \u0027a\u0027 not being zero, but the\n // benefit is lost if \u0027b\u0027 is also tested.\n // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522\n if (a == 0) {\n return 0;\n }\n\n uint256 c = a * b;\n require(c / a == b, \"SafeMath: multiplication overflow\");\n\n return c;\n }\n\n /**\n * @dev Returns the integer division of two unsigned integers. Reverts on\n * division by zero. The result is rounded towards zero.\n *\n * Counterpart to Solidity\u0027s `/` operator. Note: this function uses a\n * `revert` opcode (which leaves remaining gas untouched) while Solidity\n * uses an invalid opcode to revert (consuming all remaining gas).\n *\n * Requirements:\n * - The divisor cannot be zero.\n */\n function div(uint256 a, uint256 b) internal pure returns (uint256) {\n return div(a, b, \"SafeMath: division by zero\");\n }\n\n /**\n * @dev Returns the integer division of two unsigned integers. Reverts with custom message on\n * division by zero. The result is rounded towards zero.\n *\n * Counterpart to Solidity\u0027s `/` operator. Note: this function uses a\n * `revert` opcode (which leaves remaining gas untouched) while Solidity\n * uses an invalid opcode to revert (consuming all remaining gas).\n *\n * Requirements:\n * - The divisor cannot be zero.\n *\n * _Available since v2.4.0._\n */\n function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {\n // Solidity only automatically asserts when dividing by 0\n require(b \u003e 0, errorMessage);\n uint256 c = a / b;\n // assert(a == b * c + a % b); // There is no case in which this doesn\u0027t hold\n\n return c;\n }\n\n /**\n * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),\n * Reverts when dividing by zero.\n *\n * Counterpart to Solidity\u0027s `%` operator. This function uses a `revert`\n * opcode (which leaves remaining gas untouched) while Solidity uses an\n * invalid opcode to revert (consuming all remaining gas).\n *\n * Requirements:\n * - The divisor cannot be zero.\n */\n function mod(uint256 a, uint256 b) internal pure returns (uint256) {\n return mod(a, b, \"SafeMath: modulo by zero\");\n }\n\n /**\n * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),\n * Reverts with custom message when dividing by zero.\n *\n * Counterpart to Solidity\u0027s `%` operator. This function uses a `revert`\n * opcode (which leaves remaining gas untouched) while Solidity uses an\n * invalid opcode to revert (consuming all remaining gas).\n *\n * Requirements:\n * - The divisor cannot be zero.\n *\n * _Available since v2.4.0._\n */\n function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {\n require(b != 0, errorMessage);\n return a % b;\n }\n}\n"}}File 7 of 13: 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 8 of 13: DharmaSmartWalletImplementationV11
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,
bytes data,
bytes returnData
);
event CallFailure(
bytes32 actionID,
uint256 nonce,
address to,
bytes data,
string revertReason
);
// ABIEncoderV2 uses an array of Calls for executing generic batch calls.
struct Call {
address to;
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() 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 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 CTokenInterface {
function redeem(uint256 redeemAmount) external returns (uint256 err);
function transfer(address recipient, uint256 value) external returns (bool);
function approve(address spender, uint256 amount) external returns (bool);
function balanceOf(address account) external view returns (uint256 balance);
function allowance(address owner, address spender) external view returns (uint256);
}
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 USDCV1Interface {
function isBlacklisted(address _account) external view returns (bool);
function paused() external view returns (bool);
}
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 TradeHelperInterface {
function tradeEthForDai(
uint256 daiExpected, address target, bytes calldata data
) external payable returns (uint256 daiReceived);
}
interface RevertReasonHelperInterface {
function reason(uint256 code) external pure returns (string memory);
}
interface EtherizedInterface {
function triggerEtherTransfer(
address payable target, uint256 value
) external returns (bool success);
}
interface ConfigurationRegistryInterface {
function get(bytes32 key) external view returns (bytes32 value);
}
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 DharmaSmartWalletImplementationV11
* @author 0age
* @notice The V11 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. V11 fixes external revert reason encoding issues.
*/
contract DharmaSmartWalletImplementationV11 is
DharmaSmartWalletImplementationV1Interface,
DharmaSmartWalletImplementationV3Interface,
DharmaSmartWalletImplementationV4Interface,
DharmaSmartWalletImplementationV7Interface,
DharmaSmartWalletImplementationV8Interface,
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 = 11;
// 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 all
// funds from their smart wallet by using the Dharma Escape Hatch Registry.
DharmaEscapeHatchRegistryInterface internal constant _ESCAPE_HATCH_REGISTRY = (
DharmaEscapeHatchRegistryInterface(0x00000000005280B515004B998a944630B6C663f8)
);
// Interface with dDai, dUSDC, Dai, USDC, Sai, cSai, cDai, cUSDC, & migrator.
DTokenInterface internal constant _DDAI = DTokenInterface(
0x00000000001876eB1444c986fD502e618c587430 // mainnet
);
DTokenInterface internal constant _DUSDC = DTokenInterface(
0x00000000008943c65cAf789FFFCF953bE156f6f8 // mainnet
);
ERC20Interface internal constant _DAI = ERC20Interface(
0x6B175474E89094C44Da98b954EedeAC495271d0F // mainnet
);
ERC20Interface internal constant _USDC = ERC20Interface(
0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 // mainnet
);
CTokenInterface internal constant _CDAI = CTokenInterface(
0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643 // mainnet
);
CTokenInterface internal constant _CUSDC = CTokenInterface(
0x39AA39c021dfbaE8faC545936693aC917d5E7563 // mainnet
);
// The "trade helper" facilitates Eth-to-Dai trades in an isolated context.
TradeHelperInterface internal constant _TRADE_HELPER = TradeHelperInterface(
0x421816CDFe2073945173c0c35799ec21261fB399
);
// The "revert reason helper" contains a collection of revert reason strings.
RevertReasonHelperInterface internal constant _REVERT_REASON_HELPER = (
RevertReasonHelperInterface(0x9C0ccB765D3f5035f8b5Dd30fE375d5F4997D8E4)
);
ConfigurationRegistryInterface internal constant _CONFIG_REGISTRY = (
ConfigurationRegistryInterface(0xC5C0ead7Df3CeFC45c8F4592E3a0f1500949E75D)
);
bytes32 internal constant _ENABLE_USDC_MINTING_KEY = bytes32(
0x596746115f08448433597980d42b4541c0197187d07ffad9c7f66a471c49dbba
); // keccak256("allowAvailableUSDCToBeUsedToMintCUSDC")
// Compound returns a value of 0 to indicate success, or lack of an error.
uint256 internal constant _COMPOUND_SUCCESS = 0;
// ERC-1271 must return this magic value when `isValidSignature` is called.
bytes4 internal constant _ERC_1271_MAGIC_VALUE = bytes4(0x20c13b0b);
// Minimum supported deposit & non-maximum withdrawal size is .001 underlying.
uint256 private constant _JUST_UNDER_ONE_1000th_DAI = 999999999999999;
uint256 private constant _JUST_UNDER_ONE_1000th_USDC = 999;
// Specify the amount of gas to supply when making Ether transfers.
uint256 private constant _ETH_TRANSFER_GAS = 4999;
constructor() public {
assert(
_ENABLE_USDC_MINTING_KEY == keccak256(
bytes("allowAvailableUSDCToBeUsedToMintCUSDC")
)
);
}
/**
* @notice Accept Ether in the fallback.
*/
function () external payable {}
/**
* @notice In the initializer, set up the initial user signing key, set
* approval on the Dharma Dai and Dharma USD Coin contracts, and deposit any
* Dai or USDC already at this address to receive dDai or dUSDC. 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);
// Approve the dDai contract to transfer Dai on behalf of this contract.
if (_setFullApproval(AssetType.DAI)) {
// Get the current Dai balance on this contract.
uint256 daiBalance = _DAI.balanceOf(address(this));
// Try to deposit the full Dai balance to Dharma Dai.
_depositDharmaToken(AssetType.DAI, daiBalance);
}
// Approve the dUSDC contract to transfer USDC on behalf of this contract.
if (_setFullApproval(AssetType.USDC)) {
// Get the current USDC balance on this contract.
uint256 usdcBalance = _USDC.balanceOf(address(this));
// Try to deposit the full Dai balance to Dharma USDC.
_depositDharmaToken(AssetType.USDC, usdcBalance);
}
}
/**
* @notice Deposit all Dai and USDC currently residing at this address and
* receive Dharma Dai or Dharma USD Coin in return. Note that "repay" is not
* currently implemented, though it may be in a future implementation. If some
* step of this function fails, the function itself will still succeed, but an
* `ExternalError` with information on what went wrong will be emitted.
*/
function repayAndDeposit() external {
// Get the current Dai balance on this contract.
uint256 daiBalance = _DAI.balanceOf(address(this));
// If there is any Dai balance, check for adequate approval for dDai.
if (daiBalance > 0) {
uint256 daiAllowance = _DAI.allowance(address(this), address(_DDAI));
// If allowance is insufficient, try to set it before depositing.
if (daiAllowance < daiBalance) {
if (_setFullApproval(AssetType.DAI)) {
// Deposit the full available Dai balance to Dharma Dai.
_depositDharmaToken(AssetType.DAI, daiBalance);
}
// Otherwise, just go ahead and try the Dai deposit.
} else {
// Deposit the full available Dai balance to Dharma Dai.
_depositDharmaToken(AssetType.DAI, daiBalance);
}
}
// Get the current USDC balance on this contract.
uint256 usdcBalance = _USDC.balanceOf(address(this));
// If there is any USDC balance, check for adequate approval for dUSDC.
if (usdcBalance > 0) {
uint256 usdcAllowance = _USDC.allowance(address(this), address(_DUSDC));
// If allowance is insufficient, try to set it before depositing.
if (usdcAllowance < usdcBalance) {
if (_setFullApproval(AssetType.USDC)) {
// Deposit the full available USDC balance to Dharma USDC.
_depositDharmaToken(AssetType.USDC, usdcBalance);
}
// Otherwise, just go ahead and try the USDC deposit.
} else {
// Deposit the full available USDC balance to Dharma USDC.
_depositDharmaToken(AssetType.USDC, usdcBalance);
}
}
}
/**
* @notice Withdraw Dai to a provided recipient address by redeeming the
* underlying Dai from the dDai contract and transferring it to the recipient.
* All Dai in Dharma Dai and in the smart wallet itself can be withdrawn by
* providing an amount of uint256(-1) or 0xfff...fff. This function can be
* called directly by the account set as the global key on the Dharma Key
* Registry, or by any relayer that provides a signed message from the same
* keyholder. The nonce used for the signature must match the current nonce on
* the smart wallet, and gas supplied to the call must exceed the specified
* minimum action gas, plus the gas that will be spent before the gas check is
* reached - usually somewhere around 25,000 gas. If the withdrawal fails, an
* `ExternalError` with additional details on what went wrong will be emitted.
* Note that some dust may still be left over, even in the event of a max
* withdrawal, due to the fact that Dai has a higher precision than dDai. Also
* note that the withdrawal will fail in the event that Compound does not have
* sufficient Dai available to withdraw.
* @param amount uint256 The amount of Dai to withdraw.
* @param recipient address The account to transfer the withdrawn Dai 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 withdrawal succeeded, otherwise false.
*/
function withdrawDai(
uint256 amount,
address 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.DAIWithdrawal,
abi.encode(amount, recipient),
minimumActionGas,
userSignature,
dharmaSignature
);
// Ensure that an amount of at least 0.001 Dai has been supplied.
if (amount <= _JUST_UNDER_ONE_1000th_DAI) {
revert(_revertReason(0));
}
// Ensure that a non-zero recipient has been supplied.
if (recipient == address(0)) {
revert(_revertReason(1));
}
// Set the self-call context in order to call _withdrawDaiAtomic.
_selfCallContext = this.withdrawDai.selector;
// Make the atomic self-call - if redeemUnderlying fails on dDai, it will
// succeed but nothing will happen except firing an ExternalError event. If
// the second part of the self-call (the Dai transfer) fails, it will revert
// and roll back the first part of the call as well as fire an ExternalError
// event after returning from the failed call.
bytes memory returnData;
(ok, returnData) = address(this).call(abi.encodeWithSelector(
this._withdrawDaiAtomic.selector, amount, recipient
));
// If the atomic call failed, emit an event signifying a transfer failure.
if (!ok) {
emit ExternalError(address(_DAI), _revertReason(2));
} else {
// Set ok to false if the call succeeded but the withdrawal failed.
ok = abi.decode(returnData, (bool));
}
}
/**
* @notice Protected function that can only be called from `withdrawDai` on
* this contract. It will attempt to withdraw the supplied amount of Dai, or
* the maximum amount if specified using `uint256(-1)`, to the supplied
* recipient address by redeeming the underlying Dai from the dDai contract
* and transferring it to the recipient. An ExternalError will be emitted and
* the transfer will be skipped if the call to `redeem` or `redeemUnderlying`
* fails, and any revert will be caught by `withdrawDai` and diagnosed in
* order to emit an appropriate `ExternalError` as well.
* @param amount uint256 The amount of Dai to withdraw.
* @param recipient address The account to transfer the withdrawn Dai to.
* @return True if the withdrawal succeeded, otherwise false.
*/
function _withdrawDaiAtomic(
uint256 amount,
address recipient
) external returns (bool success) {
// Ensure caller is this contract and self-call context is correctly set.
_enforceSelfCallFrom(this.withdrawDai.selector);
// If amount = 0xfff...fff, withdraw the maximum amount possible.
bool maxWithdraw = (amount == uint256(-1));
if (maxWithdraw) {
// First attempt to redeem all dDai if there is a balance.
_withdrawMaxFromDharmaToken(AssetType.DAI);
// Then transfer all Dai to recipient if there is a balance.
require(_transferMax(_DAI, recipient, false));
success = true;
} else {
// Attempt to withdraw specified Dai from Dharma Dai before proceeding.
if (_withdrawFromDharmaToken(AssetType.DAI, amount)) {
// At this point Dai transfer should never fail - wrap it just in case.
require(_DAI.transfer(recipient, amount));
success = true;
}
}
}
/**
* @notice Withdraw USDC to a provided recipient address by redeeming the
* underlying USDC from the dUSDC contract and transferring it to recipient.
* All USDC in Dharma USD Coin and in the smart wallet itself can be withdrawn
* by providing an amount of uint256(-1) or 0xfff...fff. This function can be
* called directly by the account set as the global key on the Dharma Key
* Registry, or by any relayer that provides a signed message from the same
* keyholder. The nonce used for the signature must match the current nonce on
* the smart wallet, and gas supplied to the call must exceed the specified
* minimum action gas, plus the gas that will be spent before the gas check is
* reached - usually somewhere around 25,000 gas. If the withdrawal fails, an
* `ExternalError` with additional details on what went wrong will be emitted.
* Note that the USDC contract can be paused and also allows for blacklisting
* accounts - either of these possibilities may cause a withdrawal to fail. In
* addition, Compound may not have sufficient USDC available at the time to
* withdraw.
* @param amount uint256 The amount of USDC to withdraw.
* @param recipient address The account to transfer the withdrawn USDC 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 withdrawal succeeded, otherwise false.
*/
function withdrawUSDC(
uint256 amount,
address 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.USDCWithdrawal,
abi.encode(amount, recipient),
minimumActionGas,
userSignature,
dharmaSignature
);
// Ensure that an amount of at least 0.001 USDC has been supplied.
if (amount <= _JUST_UNDER_ONE_1000th_USDC) {
revert(_revertReason(3));
}
// Ensure that a non-zero recipient has been supplied.
if (recipient == address(0)) {
revert(_revertReason(1));
}
// Set the self-call context in order to call _withdrawUSDCAtomic.
_selfCallContext = this.withdrawUSDC.selector;
// Make the atomic self-call - if redeemUnderlying fails on dUSDC, it will
// succeed but nothing will happen except firing an ExternalError event. If
// the second part of the self-call (USDC transfer) fails, it will revert
// and roll back the first part of the call as well as fire an ExternalError
// event after returning from the failed call.
bytes memory returnData;
(ok, returnData) = address(this).call(abi.encodeWithSelector(
this._withdrawUSDCAtomic.selector, amount, recipient
));
if (!ok) {
// Find out why USDC transfer reverted (doesn't give revert reasons).
_diagnoseAndEmitUSDCSpecificError(_USDC.transfer.selector);
} else {
// Set ok to false if the call succeeded but the withdrawal failed.
ok = abi.decode(returnData, (bool));
}
}
/**
* @notice Protected function that can only be called from `withdrawUSDC` on
* this contract. It will attempt to withdraw the supplied amount of USDC, or
* the maximum amount if specified using `uint256(-1)`, to the supplied
* recipient address by redeeming the underlying USDC from the dUSDC contract
* and transferring it to the recipient. An ExternalError will be emitted and
* the transfer will be skipped if the call to `redeemUnderlying` fails, and
* any revert will be caught by `withdrawUSDC` and diagnosed in order to emit
* an appropriate ExternalError as well.
* @param amount uint256 The amount of USDC to withdraw.
* @param recipient address The account to transfer the withdrawn USDC to.
* @return True if the withdrawal succeeded, otherwise false.
*/
function _withdrawUSDCAtomic(
uint256 amount,
address recipient
) external returns (bool success) {
// Ensure caller is this contract and self-call context is correctly set.
_enforceSelfCallFrom(this.withdrawUSDC.selector);
// If amount = 0xfff...fff, withdraw the maximum amount possible.
bool maxWithdraw = (amount == uint256(-1));
if (maxWithdraw) {
// Attempt to redeem all dUSDC from Dharma USDC if there is a balance.
_withdrawMaxFromDharmaToken(AssetType.USDC);
// Then transfer all USDC to recipient if there is a balance.
require(_transferMax(_USDC, recipient, false));
success = true;
} else {
// Attempt to withdraw specified USDC from Dharma USDC before proceeding.
if (_withdrawFromDharmaToken(AssetType.USDC, amount)) {
// Ensure that the USDC transfer does not fail.
require(_USDC.transfer(recipient, amount));
success = true;
}
}
}
/**
* @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 Perform a generic call to another contract. Note that accounts with
* no code may not be specified, 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 a valid target, signatutes, and
* gas will always succeed. To determine whether the call made as part of the
* action was successful or not, either the return values or the `CallSuccess`
* or `CallFailure` event can be used.
* @param to address The contract to call.
* @param data bytes The calldata to provide when making the 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 A boolean signifying the status of the call, as well as any data
* returned from the call.
*/
function executeAction(
address to,
bytes calldata data,
uint256 minimumActionGas,
bytes calldata userSignature,
bytes calldata dharmaSignature
) external returns (bool ok, bytes memory returnData) {
// Ensure that the `to` address is a contract and is not this contract.
_ensureValidGenericCallTarget(to);
// Ensure caller and/or supplied signatures are valid and increment nonce.
(bytes32 actionID, uint256 nonce) = _validateActionAndIncrementNonce(
ActionType.Generic,
abi.encode(to, data),
minimumActionGas,
userSignature,
dharmaSignature
);
// Note: from this point on, there are no reverts (apart from out-of-gas or
// call-depth-exceeded) originating from this action. However, the call
// itself may revert, in which case the function will return `false`, along
// with the revert reason encoded as bytes, and fire an CallFailure event.
// Perform the action via low-level call and set return values using result.
(ok, returnData) = to.call(data);
// Emit a CallSuccess or CallFailure event based on the outcome of the call.
if (ok) {
// Note: while the call succeeded, the action may still have "failed"
// (for example, successful calls to Compound can still return an error).
emit CallSuccess(actionID, false, nonce, to, data, returnData);
} else {
// Note: while the call failed, the nonce will still be incremented, which
// will invalidate all supplied signatures.
emit CallFailure(actionID, nonce, to, data, _decodeRevertReason(returnData));
}
}
/**
* @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()` at any point to "sweep" the entire Dai, USDC,
* residual cDai, cUSDC, dDai, dUSDC, and Ether balance 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 Swap Ether for Dai and use it to mint Dharma Dai. The trade is
* facilitated by a "trade helper" contract in order to protect against
* malicious calls related to processing swaps via potentially unsafe call
* targets or other parameters. In the event that a swap does not result in
* sufficient Dai being received, the swap will be rolled back. In either
* case the nonce will still be incremented as long as signatures are valid.
* @param ethToSupply uint256 The Ether to supply as part of the swap.
* @param minimumDaiReceived uint256 The minimum amount of Dai that must be
* received in exchange for the supplied Ether.
* @param target address The contract that the trade helper should call in
* order to facilitate the swap.
* @param data bytes The payload that will be passed to the target, along with
* the supplied Ether, by the trade helper in order to facilitate the swap.
* @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 `getEthForDaiActionID` 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 `getEthForDaiActionIDActionID` is prefixed and hashed to
* create the signed message.
*/
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) {
// Ensure caller and/or supplied signatures are valid and increment nonce.
_validateActionAndIncrementNonce(
ActionType.TradeEthForDai,
abi.encode(ethToSupply, minimumDaiReceived, target, data),
minimumActionGas,
userSignature,
dharmaSignature
);
// Ensure that an amount of at least 0.001 Dai will be received.
if (minimumDaiReceived <= _JUST_UNDER_ONE_1000th_DAI) {
revert(_revertReason(31));
}
// Set the self-call context in order to call _tradeEthForDaiAndMintDDaiAtomic.
_selfCallContext = this.tradeEthForDaiAndMintDDai.selector;
// Make the atomic self-call - if the swap fails or the received dai is not
// greater than or equal to the requirement, it will revert and roll back the
// atomic call as well as fire an ExternalError. If dDai is not successfully
// minted, the swap will succeed but an ExternalError for dDai will be fired.
bytes memory returnData;
(ok, returnData) = address(this).call(abi.encodeWithSelector(
this._tradeEthForDaiAndMintDDaiAtomic.selector,
ethToSupply, minimumDaiReceived, target, data
));
// If the atomic call failed, emit an event signifying a trade failure.
if (!ok) {
emit ExternalError(
address(_TRADE_HELPER), _decodeRevertReason(returnData)
);
}
}
function _tradeEthForDaiAndMintDDaiAtomic(
uint256 ethToSupply,
uint256 minimumDaiReceived,
address target,
bytes calldata data
) external returns (bool ok, bytes memory returnData) {
// Ensure caller is this contract and self-call context is correctly set.
_enforceSelfCallFrom(this.tradeEthForDaiAndMintDDai.selector);
// Do swap using supplied Ether amount, minimum Dai, target, and data.
uint256 daiReceived = _TRADE_HELPER.tradeEthForDai.value(ethToSupply)(
minimumDaiReceived, target, data
);
// Ensure that sufficient Dai was returned as a result of the swap.
if (daiReceived < minimumDaiReceived) {
revert(_revertReason(32));
}
// Attempt to deposit the dai received and mint Dharma Dai.
_depositDharmaToken(AssetType.DAI, daiReceived);
}
/**
* @notice Allow the designated escape hatch account to redeem and "sweep"
* the entire Dai, USDC, residual dDai, dUSDC, cDai, cUSDC, & Ether balance
* from the smart wallet. The call will revert for any other caller, or if
* there is no escape hatch account on this smart wallet. First, an attempt
* will be made to redeem any dDai or dUSDC that is currently deposited in a
* dToken. Then, attempts will be made to transfer any balance in Dai, USDC,
* residual cDai & cUSDC, and Ether to the escape hatch account. If any
* portion of this operation does not succeed, it will simply be skipped,
* allowing the rest of the operation to proceed. Finally, an `Escaped` event
* will be emitted. No value is returned from this function - it will either
* succeed or revert.
*/
function escape() 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));
}
// Attempt to redeem all dDai for Dai on Dharma Dai.
_withdrawMaxFromDharmaToken(AssetType.DAI);
// Attempt to redeem all dUSDC for USDC on Dharma USDC.
_withdrawMaxFromDharmaToken(AssetType.USDC);
// Attempt to transfer the total Dai balance to the caller.
_transferMax(_DAI, msg.sender, true);
// Attempt to transfer the total USDC balance to the caller.
_transferMax(_USDC, msg.sender, true);
// Attempt to transfer any residual cDai to the caller.
_transferMax(ERC20Interface(address(_CDAI)), msg.sender, true);
// Attempt to transfer any residual cUSDC to the caller.
_transferMax(ERC20Interface(address(_CUSDC)), msg.sender, true);
// Attempt to transfer any residual dDai to the caller.
_transferMax(ERC20Interface(address(_DDAI)), msg.sender, true);
// Attempt to transfer any residual dUSDC to the caller.
_transferMax(ERC20Interface(address(_DUSDC)), msg.sender, true);
// 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);
}
// 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);
}
/**
* @notice This call is no longer supported.
*/
function migrateSaiToDai() external {
revert();
}
/**
* @notice This call is no longer supported.
*/
function migrateCSaiToDDai() external {
revert();
}
/**
* @notice Redeem all available cDAI for Dai and use that Dai to mint dDai. If
* any step in the process fails, the call will revert and prior steps will be
* rolled back. Also note that existing Sai and Dai are not included as part
* of this operation.
*/
function migrateCDaiToDDai() external {
_migrateCTokenToDToken(AssetType.DAI);
}
/**
* @notice Redeem all available cUSDC for USDC and use that USDC to mint
* dUSDC. If any step in the process fails, the call will revert and prior
* steps will be rolled back. Also note that existing USDC is not included as
* part of this operation.
*/
function migrateCUSDCToDUSDC() external {
_migrateCTokenToDToken(AssetType.USDC);
}
/**
* @notice View function to retrieve the Dai and USDC balances held by the
* smart wallet, both directly and held in Dharma Dai and Dharma USD Coin, as
* well as the Ether balance (the underlying dEther balance will always return
* zero in this implementation, as there is no dEther yet).
* @return The Dai balance, the USDC balance, the Ether balance, the
* underlying Dai balance of the dDai balance, and the underlying USDC balance
* of the dUSDC balance (zero will always be returned as the underlying Ether
* balance of the dEther balance in this implementation).
*/
function getBalances() external view returns (
uint256 daiBalance,
uint256 usdcBalance,
uint256 etherBalance,
uint256 dDaiUnderlyingDaiBalance,
uint256 dUsdcUnderlyingUsdcBalance,
uint256 dEtherUnderlyingEtherBalance // always returns 0
) {
daiBalance = _DAI.balanceOf(address(this));
usdcBalance = _USDC.balanceOf(address(this));
etherBalance = address(this).balance;
dDaiUnderlyingDaiBalance = _DDAI.balanceOfUnderlying(address(this));
dUsdcUnderlyingUsdcBalance = _DUSDC.balanceOfUnderlying(address(this));
}
/**
* @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 in V8 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 in V8 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 View function that 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 action. The current nonce will be used, which means that it will
* only be valid for the next action taken.
* @param to address The target to call into as part of the generic action.
* @param data bytes The data to supply when calling into the target.
* @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 getNextGenericActionID(
address to,
bytes calldata data,
uint256 minimumActionGas
) external view returns (bytes32 actionID) {
// Determine the actionID - this serves as a signature hash for an action.
actionID = _getActionID(
ActionType.Generic,
abi.encode(to, data),
_nonce,
minimumActionGas,
_userSigningKey,
_getDharmaSigningKey()
);
}
/**
* @notice View function that 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 action. Any nonce value may be supplied, which enables constructing
* valid message hashes for multiple future actions ahead of time.
* @param to address The target to call into as part of the generic action.
* @param data bytes The data to supply when calling into the target.
* @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 getGenericActionID(
address to,
bytes calldata data,
uint256 nonce,
uint256 minimumActionGas
) external view returns (bytes32 actionID) {
// Determine the actionID - this serves as a signature hash for an action.
actionID = _getActionID(
ActionType.Generic,
abi.encode(to, data),
nonce,
minimumActionGas,
_userSigningKey,
_getDharmaSigningKey()
);
}
/**
* @notice View function that 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 an
* Eth-to-Dai swap. The current nonce will be used, which means that it will
* only be valid for the next action taken.
* @param ethToSupply uint256 The Ether to supply as part of the swap.
* @param minimumDaiReceived uint256 The minimum amount of Dai that must be
* received in exchange for the supplied Ether.
* @param target address The contract that the trade helper should call in
* order to facilitate the swap.
* @param data bytes The payload that will be passed to the target, along with
* the supplied Ether, by the trade helper in order to facilitate the swap.
* @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 getNextEthForDaiActionID(
uint256 ethToSupply,
uint256 minimumDaiReceived,
address target,
bytes calldata data,
uint256 minimumActionGas
) external view returns (bytes32 actionID) {
// Determine the actionID - this serves as a signature hash for an action.
actionID = _getActionID(
ActionType.TradeEthForDai,
abi.encode(ethToSupply, minimumDaiReceived, target, data),
_nonce,
minimumActionGas,
_userSigningKey,
_getDharmaSigningKey()
);
}
/**
* @notice View function that 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 an
* Eth-to-Dai swap. Any nonce value may be supplied, which enables
* constructing valid message hashes for multiple future actions ahead of
* time.
* @param ethToSupply uint256 The Ether to supply as part of the swap.
* @param minimumDaiReceived uint256 The minimum amount of Dai that must be
* received in exchange for the supplied Ether.
* @param target address The contract that the trade helper should call in
* order to facilitate the swap.
* @param data bytes The payload that will be passed to the target, along with
* the supplied Ether, by the trade helper in order to facilitate the swap.
* @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 getEthForDaiActionID(
uint256 ethToSupply,
uint256 minimumDaiReceived,
address target,
bytes calldata data,
uint256 nonce,
uint256 minimumActionGas
) external view returns (bytes32 actionID) {
// Determine the actionID - this serves as a signature hash for an action.
actionID = _getActionID(
ActionType.TradeEthForDai,
abi.encode(ethToSupply, minimumDaiReceived, target, data),
nonce,
minimumActionGas,
_userSigningKey,
_getDharmaSigningKey()
);
}
/**
* @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, 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 the
* corresponding `CallSuccess` or `CallFailure` event can be used - note that
* even calls that return a success status will have been 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 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++) {
_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
)
);
// 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,
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,
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 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(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 data 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 setting the allowance of a given ERC20 asset
* to the maximum value. This enables the corresponding dToken for the asset
* to pull in tokens in order to make deposits.
* @param asset uint256 The ID of the asset, either Dai (0) or USDC (1).
* @return True if the approval succeeded, otherwise false.
*/
function _setFullApproval(AssetType asset) internal returns (bool ok) {
// Get asset's underlying token address and corresponding dToken address.
address token;
address dToken;
if (asset == AssetType.DAI) {
token = address(_DAI);
dToken = address(_DDAI);
} else {
token = address(_USDC);
dToken = address(_DUSDC);
}
// Approve dToken contract to transfer underlying on behalf of this wallet.
(ok, ) = address(token).call(abi.encodeWithSelector(
// Note: since both Tokens have the same interface, just use DAI's.
_DAI.approve.selector, dToken, uint256(-1)
));
// Emit a corresponding event if the approval failed.
if (!ok) {
if (asset == AssetType.DAI) {
emit ExternalError(address(_DAI), _revertReason(17));
} else {
// Find out why USDC transfer reverted (it doesn't give revert reasons).
_diagnoseAndEmitUSDCSpecificError(_USDC.approve.selector);
}
}
}
/**
* @notice Internal function for depositing a given ERC20 asset and balance on
* the corresponding dToken. No value is returned, as no additional steps need
* to be conditionally performed after the deposit.
* @param asset uint256 The ID of the asset, either Dai (0) or USDC (1).
* @param balance uint256 The amount of the asset to deposit. Note that an
* attempt to deposit "dust" (i.e. very small amounts) may result in fewer
* dTokens being minted than is implied by the current exchange rate due to a
* lack of sufficient precision on the tokens in question. USDC deposits are
* also dependent on a flag being set on the Configuration Registry contract.
*/
function _depositDharmaToken(AssetType asset, uint256 balance) internal {
// Only perform a deposit if the balance is at least .001 Dai or USDC.
if (
asset == AssetType.DAI && balance > _JUST_UNDER_ONE_1000th_DAI ||
asset == AssetType.USDC && (
balance > _JUST_UNDER_ONE_1000th_USDC &&
uint256(_CONFIG_REGISTRY.get(_ENABLE_USDC_MINTING_KEY)) != 0
)
) {
// Get dToken address for the asset type.
address dToken = asset == AssetType.DAI ? address(_DDAI) : address(_DUSDC);
// Attempt to mint the balance on the dToken contract.
(bool ok, bytes memory data) = dToken.call(abi.encodeWithSelector(
// Note: since both dTokens have the same interface, just use dDai's.
_DDAI.mint.selector, balance
));
// Log an external error if something went wrong with the attempt.
_checkDharmaTokenInteractionAndLogAnyErrors(
asset, _DDAI.mint.selector, ok, data
);
}
}
/**
* @notice Internal function for withdrawing a given 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).
* @param balance uint256 The amount of the asset to withdraw, denominated in
* the underlying token. Note that an attempt to withdraw "dust" (i.e. very
* small amounts) may result in 0 underlying tokens being redeemed, or in
* fewer tokens being redeemed than is implied by the current exchange rate
* (due to lack of sufficient precision on the tokens).
* @return True if the withdrawal succeeded, otherwise false.
*/
function _withdrawFromDharmaToken(
AssetType asset, uint256 balance
) internal returns (bool success) {
// Get dToken address for the asset type. (No custom ETH withdrawal action.)
address dToken = asset == AssetType.DAI ? address(_DDAI) : address(_DUSDC);
// Attempt to redeem the underlying balance from the dToken contract.
(bool ok, bytes memory data) = dToken.call(abi.encodeWithSelector(
// Note: function selector is the same for each dToken so just use dDai's.
_DDAI.redeemUnderlying.selector, balance
));
// Log an external error if something went wrong with the attempt.
success = _checkDharmaTokenInteractionAndLogAnyErrors(
asset, _DDAI.redeemUnderlying.selector, ok, data
);
}
/**
* @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 in V8 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 Use all available cTokens to mint the respective dTokens. If any
* step in the process fails, the call will revert and prior steps will be
* rolled back. Also note that existing underlying tokens are not included as
* part of this operation.
*/
function _migrateCTokenToDToken(AssetType token) internal {
CTokenInterface cToken;
DTokenInterface dToken;
if (token == AssetType.DAI) {
cToken = _CDAI;
dToken = _DDAI;
} else {
cToken = _CUSDC;
dToken = _DUSDC;
}
// Get the current cToken balance for this account.
uint256 balance = cToken.balanceOf(address(this));
// Only perform the conversion if there is a non-zero balance.
if (balance > 0) {
// If the allowance is insufficient, set it before depositing.
if (cToken.allowance(address(this), address(dToken)) < balance) {
if (!cToken.approve(address(dToken), uint256(-1))) {
revert(_revertReason(23));
}
}
// Deposit the new balance on the Dharma Token.
if (dToken.mintViaCToken(balance) == 0) {
revert(_revertReason(24));
}
}
}
/**
* @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 diagnose the reason that a call to the USDC
* contract failed and to emit a corresponding ExternalError event. USDC can
* blacklist accounts and pause the contract, which can both cause a transfer
* or approval to fail.
* @param functionSelector bytes4 The function selector that was called on the
* USDC contract.
*/
function _diagnoseAndEmitUSDCSpecificError(bytes4 functionSelector) internal {
// Determine the name of the function that was called on USDC.
string memory functionName;
if (functionSelector == _USDC.transfer.selector) {
functionName = "transfer";
} else {
functionName = "approve";
}
USDCV1Interface usdcNaughty = USDCV1Interface(address(_USDC));
// Find out why USDC transfer reverted (it doesn't give revert reasons).
if (usdcNaughty.isBlacklisted(address(this))) {
emit ExternalError(
address(_USDC),
string(
abi.encodePacked(
functionName, " failed - USDC has blacklisted this user."
)
)
);
} else { // Note: `else if` breaks coverage.
if (usdcNaughty.paused()) {
emit ExternalError(
address(_USDC),
string(
abi.encodePacked(
functionName, " failed - USDC contract is currently paused."
)
)
);
} else {
emit ExternalError(
address(_USDC),
string(
abi.encodePacked(
"USDC contract reverted on ", functionName, "."
)
)
);
}
}
}
/**
* @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 in V8 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 in V8 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 in V8 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 9 of 13: 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 10 of 13: 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 11 of 13: 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."
);
}
}File 12 of 13: DharmaTradeReserveUpgradeBeacon
pragma solidity 0.5.11; // optimization runs: 200, evm version: petersburg
/**
* @title DharmaTradeReserveUpgradeBeacon
* @author 0age
* @notice This contract holds the address of the current implementation for
* the Dharma Trade Reserve contract and lets a controller update that address
* in storage.
*/
contract DharmaTradeReserveUpgradeBeacon {
// 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(
0x48030C8236C20DC09C60B4C39d17AFD3c2882e0D
);
/**
* @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 13 of 13: DharmaTradeReserveV13Implementation
pragma solidity 0.5.17; // optimization runs: 200, evm version: istanbul
interface DharmaTradeReserveV13Interface {
event Trade(
address account,
address suppliedAsset,
address receivedAsset,
address retainedAsset,
uint256 suppliedAmount,
uint256 recievedAmount,
uint256 retainedAmount
);
event RoleModified(Role indexed role, address account);
event RolePaused(Role indexed role);
event RoleUnpaused(Role indexed role);
event EtherReceived(address sender, uint256 amount);
enum Role { // #
DEPOSIT_MANAGER, // 0
ADJUSTER, // 1
WITHDRAWAL_MANAGER, // 2
RESERVE_TRADER, // 3
PAUSER // 4
}
enum TradeType {
DAI_TO_TOKEN,
DAI_TO_ETH,
ETH_TO_DAI,
TOKEN_TO_DAI,
ETH_TO_TOKEN,
TOKEN_TO_ETH
}
struct RoleStatus {
address account;
bool paused;
}
function tradeDaiForEther(
uint256 daiAmount, uint256 quotedEtherAmount, uint256 deadline
) external returns (uint256 totalDaiSold);
function tradeDaiForEtherV2(
uint256 daiAmount, uint256 quotedEtherAmount, uint256 deadline
) external returns (uint256 totalDaiSold);
function tradeEtherForDai(
uint256 quotedDaiAmount, uint256 deadline
) external payable returns (uint256 totalDaiBought);
function tradeEtherForDaiV2(
uint256 quotedDaiAmount, uint256 deadline
) external payable returns (uint256 totalDaiBought);
function tradeDaiForToken(
address token,
uint256 daiAmount,
uint256 quotedTokenAmount,
uint256 deadline,
bool routeThroughEther
) external returns (uint256 totalDaiSold);
function tradeTokenForDai(
ERC20Interface token,
uint256 tokenAmount,
uint256 quotedDaiAmount,
uint256 deadline,
bool routeThroughEther
) external returns (uint256 totalDaiBought);
function tradeTokenForEther(
ERC20Interface token,
uint256 tokenAmount,
uint256 quotedEtherAmount,
uint256 deadline
) external returns (uint256 totalEtherBought);
function tradeEtherForToken(
address token, uint256 quotedTokenAmount, uint256 deadline
) external payable returns (uint256 totalEtherSold);
function tradeEtherForTokenUsingEtherizer(
address token,
uint256 etherAmount,
uint256 quotedTokenAmount,
uint256 deadline
) external returns (uint256 totalEtherSold);
function tradeDaiForEtherUsingReserves(
uint256 daiAmountFromReserves, uint256 quotedEtherAmount, uint256 deadline
) external returns (uint256 totalDaiSold);
function tradeDaiForEtherUsingReservesV2(
uint256 daiAmountFromReserves, uint256 quotedEtherAmount, uint256 deadline
) external returns (uint256 totalDaiSold);
function tradeEtherForDaiUsingReservesAndMintDDai(
uint256 etherAmountFromReserves, uint256 quotedDaiAmount, uint256 deadline
) external returns (uint256 totalDaiBought, uint256 totalDDaiMinted);
function tradeEtherForDaiUsingReservesAndMintDDaiV2(
uint256 etherAmountFromReserves, uint256 quotedDaiAmount, uint256 deadline
) external returns (uint256 totalDaiBought, uint256 totalDDaiMinted);
function tradeDaiForTokenUsingReserves(
address token,
uint256 daiAmountFromReserves,
uint256 quotedTokenAmount,
uint256 deadline,
bool routeThroughEther
) external returns (uint256 totalDaiSold);
function tradeTokenForDaiUsingReservesAndMintDDai(
ERC20Interface token,
uint256 tokenAmountFromReserves,
uint256 quotedDaiAmount,
uint256 deadline,
bool routeThroughEther
) external returns (uint256 totalDaiBought, uint256 totalDDaiMinted);
function tradeTokenForEtherUsingReserves(
ERC20Interface token,
uint256 tokenAmountFromReserves,
uint256 quotedEtherAmount,
uint256 deadline
) external returns (uint256 totalEtherBought);
function tradeEtherForTokenUsingReserves(
address token,
uint256 etherAmountFromReserves,
uint256 quotedTokenAmount,
uint256 deadline
) external returns (uint256 totalEtherSold);
function finalizeEtherDeposit(
address payable smartWallet,
address initialUserSigningKey,
uint256 etherAmount
) external;
function finalizeDaiDeposit(
address smartWallet, address initialUserSigningKey, uint256 daiAmount
) external;
function finalizeDharmaDaiDeposit(
address smartWallet, address initialUserSigningKey, uint256 dDaiAmount
) external;
function mint(uint256 daiAmount) external returns (uint256 dDaiMinted);
function redeem(uint256 dDaiAmount) external returns (uint256 daiReceived);
function tradeDDaiForUSDC(
uint256 daiEquivalentAmount, uint256 quotedUSDCAmount
) external returns (uint256 usdcReceived);
function tradeUSDCForDDai(
uint256 usdcAmount, uint256 quotedDaiEquivalentAmount
) external returns (uint256 dDaiMinted);
function withdrawUSDC(address recipient, uint256 usdcAmount) external;
function withdrawDai(address recipient, uint256 daiAmount) external;
function withdrawDharmaDai(address recipient, uint256 dDaiAmount) external;
function withdrawUSDCToPrimaryRecipient(uint256 usdcAmount) external;
function withdrawDaiToPrimaryRecipient(uint256 usdcAmount) external;
function withdrawEther(
address payable recipient, uint256 etherAmount
) external;
function withdraw(
ERC20Interface token, address recipient, uint256 amount
) external returns (bool success);
function callAny(
address payable target, uint256 amount, bytes calldata data
) external returns (bool ok, bytes memory returnData);
function setDaiLimit(uint256 daiAmount) external;
function setEtherLimit(uint256 daiAmount) external;
function setPrimaryUSDCRecipient(address recipient) external;
function setPrimaryDaiRecipient(address recipient) external;
function setRole(Role role, address account) external;
function removeRole(Role role) external;
function pause(Role role) external;
function unpause(Role role) external;
function isPaused(Role role) external view returns (bool paused);
function isRole(Role role) external view returns (bool hasRole);
function isDharmaSmartWallet(
address smartWallet, address initialUserSigningKey
) external view returns (bool dharmaSmartWallet);
function getDepositManager() external view returns (address depositManager);
function getAdjuster() external view returns (address adjuster);
function getReserveTrader() external view returns (address reserveTrader);
function getWithdrawalManager() external view returns (address withdrawalManager);
function getPauser() external view returns (address pauser);
function getReserves() external view returns (
uint256 dai, uint256 dDai, uint256 dDaiUnderlying
);
function getDaiLimit() external view returns (
uint256 daiAmount, uint256 dDaiAmount
);
function getEtherLimit() external view returns (uint256 etherAmount);
function getPrimaryUSDCRecipient() external view returns (
address recipient
);
function getPrimaryDaiRecipient() external view returns (
address recipient
);
function getImplementation() external view returns (address implementation);
function getVersion() external view returns (uint256 version);
}
interface ERC20Interface {
function balanceOf(address) external view returns (uint256);
function approve(address, uint256) external returns (bool);
function allowance(address, address) external view returns (uint256);
function transfer(address, uint256) external returns (bool);
function transferFrom(address, address, uint256) external returns (bool);
}
interface DTokenInterface {
function mint(uint256 underlyingToSupply) external returns (uint256 dTokensMinted);
function redeem(uint256 dTokensToBurn) external returns (uint256 underlyingReceived);
function redeemUnderlying(uint256 underlyingToReceive) external returns (uint256 dTokensBurned);
function balanceOf(address) external view returns (uint256);
function balanceOfUnderlying(address) external view returns (uint256);
function transfer(address, uint256) external returns (bool);
function approve(address, uint256) external returns (bool);
function exchangeRateCurrent() external view returns (uint256);
}
interface TradeHelperInterface {
function tradeUSDCForDDai(
uint256 amountUSDC,
uint256 quotedDaiEquivalentAmount
) external returns (uint256 dDaiMinted);
function tradeDDaiForUSDC(
uint256 amountDai,
uint256 quotedUSDCAmount
) external returns (uint256 usdcReceived);
function getExpectedDai(uint256 usdc) external view returns (uint256 dai);
function getExpectedUSDC(uint256 dai) external view returns (uint256 usdc);
}
interface UniswapV1Interface {
function ethToTokenSwapInput(
uint256 minTokens, uint256 deadline
) external payable returns (uint256 tokensBought);
function tokenToEthTransferOutput(
uint256 ethBought, uint256 maxTokens, uint256 deadline, address recipient
) external returns (uint256 tokensSold);
}
interface UniswapV2Interface {
function swapTokensForExactTokens(
uint256 amountOut,
uint256 amountInMax,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
function swapExactTokensForTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
function swapExactTokensForETH(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
function swapTokensForExactETH(
uint256 amountOut,
uint256 amountInMax,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
function swapETHForExactTokens(
uint256 amountOut,
address[] calldata path,
address to,
uint256 deadline
) external payable returns (uint256[] memory amounts);
function swapExactETHForTokens(
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external payable returns (uint256[] memory amounts);
}
interface EtherReceiverInterface {
function settleEther() external;
}
library SafeMath {
function add(uint256 a, uint256 b) internal pure returns (uint256 c) {
c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a, "SafeMath: subtraction overflow");
return a - b;
}
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) return 0;
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0, "SafeMath: division by zero");
return a / b;
}
}
/**
* @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 {
event OwnershipTransferred(
address indexed previousOwner,
address indexed newOwner
);
address private _owner;
address private _newPotentialOwner;
/**
* @dev Allows a new account (`newOwner`) to accept ownership.
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) external 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() external 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() external {
require(
msg.sender == _newPotentialOwner,
"TwoStepOwnable: current owner must set caller as new potential owner."
);
delete _newPotentialOwner;
emit OwnershipTransferred(_owner, msg.sender);
_owner = msg.sender;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() external view returns (address) {
return _owner;
}
/**
* @dev Returns true if the caller is the current owner.
*/
function isOwner() public view returns (bool) {
return msg.sender == _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(isOwner(), "TwoStepOwnable: caller is not the owner.");
_;
}
}
/**
* @title DharmaTradeReserveV13Implementation
* @author 0age
* @notice This contract manages Dharma's reserves. It designates a collection of
* "roles" - these are dedicated accounts that can be modified by the owner, and
* that can trigger specific functionality on the reserve. These roles are:
* - depositManager (0): initiates Eth / token transfers to smart wallets
* - adjuster (1): mints / redeems Dai, and swaps USDC, for dDai
* - withdrawalManager (2): initiates token transfers to recipients set by owner
* - reserveTrader (3): initiates trades using funds held in reserve
* - pauser (4): pauses any role (only the owner is then able to unpause it)
*
* When finalizing deposits, the deposit manager must adhere to two constraints:
* - it must provide "proof" that the recipient is a smart wallet by including
* the initial user signing key used to derive the smart wallet address
* - it must not attempt to transfer more Eth, Dai, or the Dai-equivalent
* value of Dharma Dai, than the current "limit" set by the owner.
*
* Note that "proofs" can be validated via `isSmartWallet`.
*/
contract DharmaTradeReserveV13Implementation is DharmaTradeReserveV13Interface, TwoStepOwnable {
using SafeMath for uint256;
// Maintain a role status mapping with assigned accounts and paused states.
mapping(uint256 => RoleStatus) private _roles;
// Maintain a "primary recipient" the withdrawal manager can transfer Dai to.
address private _primaryDaiRecipient;
// Maintain a "primary recipient" the withdrawal manager can transfer USDC to.
address private _primaryUSDCRecipient;
// Maintain a maximum allowable transfer size (in Dai) for the deposit manager.
uint256 private _daiLimit;
// Maintain a maximum allowable transfer size (in Ether) for the deposit manager.
uint256 private _etherLimit;
bool private _originatesFromReserveTrader; // unused, don't change storage layout
uint256 private constant _VERSION = 13;
// This contract interacts with USDC, Dai, and Dharma Dai.
ERC20Interface internal constant _USDC = ERC20Interface(
0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 // mainnet
);
ERC20Interface internal constant _DAI = ERC20Interface(
0x6B175474E89094C44Da98b954EedeAC495271d0F // mainnet
);
ERC20Interface internal constant _ETHERIZER = ERC20Interface(
0x723B51b72Ae89A3d0c2a2760f0458307a1Baa191
);
DTokenInterface internal constant _DDAI = DTokenInterface(
0x00000000001876eB1444c986fD502e618c587430
);
TradeHelperInterface internal constant _TRADE_HELPER = TradeHelperInterface(
0x9328F2Fb3e85A4d24Adc2f68F82737183e85691d
);
UniswapV1Interface internal constant _UNISWAP_DAI = UniswapV1Interface(
0x2a1530C4C41db0B0b2bB646CB5Eb1A67b7158667
);
UniswapV2Interface internal constant _UNISWAP_ROUTER = UniswapV2Interface(
0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D
);
EtherReceiverInterface internal constant _ETH_RECEIVER = EtherReceiverInterface(
0x0EBE1a9CBF4e27D507A5f1b51CC308B727D956C6
);
address internal constant _WETH = address(
0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
);
// The "Create2 Header" is used to compute smart wallet deployment addresses.
bytes21 internal constant _CREATE2_HEADER = bytes21(
0xfffc00c80b0000007f73004edb00094cad80626d8d // control character + factory
);
// The "Wallet creation code" header & footer are also used to derive wallets.
bytes internal constant _WALLET_CREATION_CODE_HEADER = hex"60806040526040516104423803806104428339818101604052602081101561002657600080fd5b810190808051604051939291908464010000000082111561004657600080fd5b90830190602082018581111561005b57600080fd5b825164010000000081118282018810171561007557600080fd5b82525081516020918201929091019080838360005b838110156100a257818101518382015260200161008a565b50505050905090810190601f1680156100cf5780820380516001836020036101000a031916815260200191505b5060405250505060006100e661019e60201b60201c565b6001600160a01b0316826040518082805190602001908083835b6020831061011f5780518252601f199092019160209182019101610100565b6001836020036101000a038019825116818451168082178552505050505050905001915050600060405180830381855af49150503d806000811461017f576040519150601f19603f3d011682016040523d82523d6000602084013e610184565b606091505b5050905080610197573d6000803e3d6000fd5b50506102be565b60405160009081906060906e26750c571ce882b17016557279adaa9083818181855afa9150503d80600081146101f0576040519150601f19603f3d011682016040523d82523d6000602084013e6101f5565b606091505b509150915081819061029f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825283818151815260200191508051906020019080838360005b8381101561026457818101518382015260200161024c565b50505050905090810190601f1680156102915780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b508080602001905160208110156102b557600080fd5b50519392505050565b610175806102cd6000396000f3fe608060405261001461000f610016565b61011c565b005b60405160009081906060906e26750c571ce882b17016557279adaa9083818181855afa9150503d8060008114610068576040519150601f19603f3d011682016040523d82523d6000602084013e61006d565b606091505b50915091508181906100fd5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b838110156100c25781810151838201526020016100aa565b50505050905090810190601f1680156100ef5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b5080806020019051602081101561011357600080fd5b50519392505050565b3660008037600080366000845af43d6000803e80801561013b573d6000f35b3d6000fdfea265627a7a7231582020202020202055706772616465426561636f6e50726f7879563120202020202064736f6c634300050b003200000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000024c4d66de8000000000000000000000000";
bytes28 internal constant _WALLET_CREATION_CODE_FOOTER = bytes28(
0x00000000000000000000000000000000000000000000000000000000
);
// Include a payable fallback so that the contract can receive Ether payments.
function () external payable {
emit EtherReceived(msg.sender, msg.value);
}
/**
* @notice Pull in `daiAmount` Dai from the caller, trade it for Ether using
* Uniswap, and return `quotedEtherAmount` Ether to the caller.
* @param daiAmount uint256 The amount of Dai to supply when trading for Ether.
* @param quotedEtherAmount uint256 The amount of Ether to return to the caller.
* @param deadline uint256 The timestamp the order is valid until.
* @return The amount of Dai sold as part of the trade.
*/
function tradeDaiForEther(
uint256 daiAmount,
uint256 quotedEtherAmount,
uint256 deadline
) external returns (uint256 totalDaiSold) {
// Transfer the Dai from the caller and revert on failure.
_transferInToken(_DAI, msg.sender, daiAmount);
// Trade the Dai for the quoted Ether amount on Uniswap and send to caller.
totalDaiSold = _UNISWAP_DAI.tokenToEthTransferOutput(
quotedEtherAmount, daiAmount, deadline, msg.sender
);
_fireTradeEvent(
false, // not from reserves
TradeType.DAI_TO_ETH,
address(0),
daiAmount,
quotedEtherAmount,
daiAmount.sub(totalDaiSold)
);
}
/**
* @notice Pull in `daiAmount` Dai from the caller, trade it for Ether using
* UniswapV2, and return `quotedEtherAmount` Ether to the caller.
* @param daiAmount uint256 The amount of Dai to supply when trading for Ether.
* @param quotedEtherAmount uint256 The amount of Ether to return to the caller.
* @param deadline uint256 The timestamp the order is valid until.
* @return The amount of Dai sold as part of the trade.
*/
function tradeDaiForEtherV2(
uint256 daiAmount,
uint256 quotedEtherAmount,
uint256 deadline
) external returns (uint256 totalDaiSold) {
// Transfer the Dai from the caller and revert on failure.
_transferInToken(_DAI, msg.sender, daiAmount);
// Establish path from Dai to Ether.
(address[] memory path, uint256[] memory amounts) = _createPathAndAmounts(
address(_DAI), _WETH, false
);
// Trade Dai for quoted Ether amount on Uniswap (send to the caller).
amounts = _UNISWAP_ROUTER.swapTokensForExactETH(
quotedEtherAmount, daiAmount, path, msg.sender, deadline
);
totalDaiSold = amounts[0];
_fireTradeEvent(
false, // not from reserves
TradeType.DAI_TO_ETH,
address(0),
daiAmount,
quotedEtherAmount,
daiAmount.sub(totalDaiSold)
);
}
function tradeTokenForEther(
ERC20Interface token, uint256 tokenAmount, uint256 quotedEtherAmount, uint256 deadline
) external returns (uint256 totalEtherBought) {
// Transfer the tokens from the caller and revert on failure.
_transferInToken(token, msg.sender, tokenAmount);
// Approve Uniswap router to transfer tokens on behalf of this contract.
_grantUniswapRouterApprovalIfNecessary(token, tokenAmount);
// Establish path from target token to Ether.
(address[] memory path, uint256[] memory amounts) = _createPathAndAmounts(
address(token), _WETH, false
);
// Trade tokens for quoted Ether amount on Uniswap (send to this contract).
amounts = _UNISWAP_ROUTER.swapExactTokensForETH(
tokenAmount, quotedEtherAmount, path, address(this), deadline
);
totalEtherBought = amounts[1];
// Send quoted Ether amount to caller and revert with reason on failure.
(bool ok, ) = msg.sender.call.value(quotedEtherAmount)("");
if (!ok) {
assembly {
returndatacopy(0, 0, returndatasize)
revert(0, returndatasize)
}
}
_fireTradeEvent(
false, // not from reserves
TradeType.TOKEN_TO_ETH,
address(token),
tokenAmount,
quotedEtherAmount,
totalEtherBought.sub(quotedEtherAmount)
);
}
function tradeDaiForToken(
address token, uint256 daiAmount, uint256 quotedTokenAmount, uint256 deadline, bool routeThroughEther
) external returns (uint256 totalDaiSold) {
// Transfer the Dai from the caller and revert on failure.
_transferInToken(_DAI, msg.sender, daiAmount);
// Establish path (direct or routed through Ether) from Dai to target token.
(address[] memory path, uint256[] memory amounts) = _createPathAndAmounts(
address(_DAI), token, routeThroughEther
);
// Trade the Dai for the quoted token amount on Uniswap and send to caller.
amounts = _UNISWAP_ROUTER.swapTokensForExactTokens(
quotedTokenAmount, daiAmount, path, msg.sender, deadline
);
totalDaiSold = amounts[0];
_fireTradeEvent(
false, // not from reserves
TradeType.DAI_TO_TOKEN,
address(token),
daiAmount,
quotedTokenAmount,
daiAmount.sub(totalDaiSold)
);
}
/**
* @notice Using `daiAmountFromReserves` Dai (note that dDai will be redeemed
* if necessary), trade for Ether using Uniswap. Only the owner or the trade
* reserve role can call this function. Note that Dharma Dai will be redeemed
* to cover the Dai if there is not enough currently in the contract.
* @param daiAmountFromReserves the amount of Dai to take from reserves.
* @param quotedEtherAmount uint256 The amount of Ether requested in the trade.
* @param deadline uint256 The timestamp the order is valid until.
* @return The amount of Ether bought as part of the trade.
*/
function tradeDaiForEtherUsingReserves(
uint256 daiAmountFromReserves, uint256 quotedEtherAmount, uint256 deadline
) external onlyOwnerOr(Role.RESERVE_TRADER) returns (uint256 totalDaiSold) {
// Redeem dDai if the current Dai balance is less than is required.
_redeemDDaiIfNecessary(daiAmountFromReserves);
// Trade the Dai for the quoted Ether amount on Uniswap.
totalDaiSold = _UNISWAP_DAI.tokenToEthTransferOutput(
quotedEtherAmount,
daiAmountFromReserves,
deadline,
address(_ETH_RECEIVER)
);
// Move the Ether from the receiver to this contract (gas workaround).
_ETH_RECEIVER.settleEther();
_fireTradeEvent(
true, // from reserves
TradeType.DAI_TO_ETH,
address(0),
daiAmountFromReserves,
quotedEtherAmount,
daiAmountFromReserves.sub(totalDaiSold)
);
}
/**
* @notice Using `daiAmountFromReserves` Dai (note that dDai will be redeemed
* if necessary), trade for Ether using UniswapV2. Only the owner or the trade
* reserve role can call this function. Note that Dharma Dai will be redeemed
* to cover the Dai if there is not enough currently in the contract.
* @param daiAmountFromReserves the amount of Dai to take from reserves.
* @param quotedEtherAmount uint256 The amount of Ether requested in the trade.
* @param deadline uint256 The timestamp the order is valid until.
* @return The amount of Ether bought as part of the trade.
*/
function tradeDaiForEtherUsingReservesV2(
uint256 daiAmountFromReserves, uint256 quotedEtherAmount, uint256 deadline
) external onlyOwnerOr(Role.RESERVE_TRADER) returns (uint256 totalDaiSold) {
// Redeem dDai if the current Dai balance is less than is required.
_redeemDDaiIfNecessary(daiAmountFromReserves);
// Establish path from Dai to Ether.
(address[] memory path, uint256[] memory amounts) = _createPathAndAmounts(
address(_DAI), _WETH, false
);
// Trade Dai for quoted Ether amount on Uniswap (send to this contract).
amounts = _UNISWAP_ROUTER.swapTokensForExactETH(
quotedEtherAmount, daiAmountFromReserves, path, address(this), deadline
);
totalDaiSold = amounts[0];
_fireTradeEvent(
true, // from reserves
TradeType.DAI_TO_ETH,
address(0),
daiAmountFromReserves,
quotedEtherAmount,
daiAmountFromReserves.sub(totalDaiSold)
);
}
function tradeTokenForEtherUsingReserves(
ERC20Interface token, uint256 tokenAmountFromReserves, uint256 quotedEtherAmount, uint256 deadline
) external onlyOwnerOr(Role.RESERVE_TRADER) returns (uint256 totalEtherBought) {
// Approve Uniswap router to transfer tokens on behalf of this contract.
_grantUniswapRouterApprovalIfNecessary(token, tokenAmountFromReserves);
// Establish path from target token to Ether.
(address[] memory path, uint256[] memory amounts) = _createPathAndAmounts(
address(token), _WETH, false
);
// Trade tokens for quoted Ether amount on Uniswap (send to this contract).
amounts = _UNISWAP_ROUTER.swapExactTokensForETH(
tokenAmountFromReserves, quotedEtherAmount, path, address(this), deadline
);
totalEtherBought = amounts[1];
_fireTradeEvent(
true, // from reserves
TradeType.TOKEN_TO_ETH,
address(token),
tokenAmountFromReserves,
quotedEtherAmount,
totalEtherBought.sub(quotedEtherAmount)
);
}
/**
* @notice Accept `msg.value` Ether from the caller, trade it for Dai using
* Uniswap, and return `quotedDaiAmount` Dai to the caller.
* @param quotedDaiAmount uint256 The amount of Dai to return to the caller.
* @param deadline uint256 The timestamp the order is valid until.
* @return The amount of Dai bought as part of the trade.
*/
function tradeEtherForDai(
uint256 quotedDaiAmount,
uint256 deadline
) external payable returns (uint256 totalDaiBought) {
// Trade the Ether for the quoted Dai amount on Uniswap.
totalDaiBought = _UNISWAP_DAI.ethToTokenSwapInput.value(msg.value)(
quotedDaiAmount, deadline
);
// Transfer the Dai to the caller and revert on failure.
_transferToken(_DAI, msg.sender, quotedDaiAmount);
_fireTradeEvent(
false, // not from reserves
TradeType.ETH_TO_DAI,
address(0),
msg.value,
quotedDaiAmount,
totalDaiBought.sub(quotedDaiAmount)
);
}
/**
* @notice Accept `msg.value` Ether from the caller, trade it for Dai using
* UniswapV2, and return `quotedDaiAmount` Dai to the caller.
* @param quotedDaiAmount uint256 The amount of Dai to return to the caller.
* @param deadline uint256 The timestamp the order is valid until.
* @return The amount of Dai bought as part of the trade.
*/
function tradeEtherForDaiV2(
uint256 quotedDaiAmount,
uint256 deadline
) external payable returns (uint256 totalDaiBought) {
// Establish path from Ether to Dai.
(address[] memory path, uint256[] memory amounts) = _createPathAndAmounts(
_WETH, address(_DAI), false
);
// Trade Ether for Dai on Uniswap (send to this contract).
amounts = _UNISWAP_ROUTER.swapExactETHForTokens.value(msg.value)(
quotedDaiAmount, path, address(this), deadline
);
totalDaiBought = amounts[1];
// Transfer the Dai to the caller and revert on failure.
_transferToken(_DAI, msg.sender, quotedDaiAmount);
_fireTradeEvent(
false, // not from reserves
TradeType.ETH_TO_DAI,
address(0),
msg.value,
quotedDaiAmount,
totalDaiBought.sub(quotedDaiAmount)
);
}
function tradeEtherForToken(
address token, uint256 quotedTokenAmount, uint256 deadline
) external payable returns (uint256 totalEtherSold) {
// Establish path from Ether to target token.
(address[] memory path, uint256[] memory amounts) = _createPathAndAmounts(
_WETH, address(token), false
);
// Trade Ether for quoted token amount on Uniswap and send to caller.
amounts = _UNISWAP_ROUTER.swapETHForExactTokens.value(msg.value)(
quotedTokenAmount, path, msg.sender, deadline
);
totalEtherSold = amounts[0];
_fireTradeEvent(
false, // not from reserves
TradeType.ETH_TO_TOKEN,
address(token),
msg.value,
quotedTokenAmount,
msg.value.sub(totalEtherSold)
);
}
function tradeEtherForTokenUsingEtherizer(
address token, uint256 etherAmount, uint256 quotedTokenAmount, uint256 deadline
) external returns (uint256 totalEtherSold) {
// Transfer the Ether from the caller and revert on failure.
_transferInToken(_ETHERIZER, msg.sender, etherAmount);
// Establish path from Ether to target token.
(address[] memory path, uint256[] memory amounts) = _createPathAndAmounts(
_WETH, address(token), false
);
// Trade Ether for quoted token amount on Uniswap and send to caller.
amounts = _UNISWAP_ROUTER.swapETHForExactTokens.value(etherAmount)(
quotedTokenAmount, path, msg.sender, deadline
);
totalEtherSold = amounts[0];
_fireTradeEvent(
false, // not from reserves
TradeType.ETH_TO_TOKEN,
address(token),
etherAmount,
quotedTokenAmount,
etherAmount.sub(totalEtherSold)
);
}
function tradeTokenForDai(
ERC20Interface token, uint256 tokenAmount, uint256 quotedDaiAmount, uint256 deadline, bool routeThroughEther
) external returns (uint256 totalDaiBought) {
// Transfer the token from the caller and revert on failure.
_transferInToken(token, msg.sender, tokenAmount);
// Approve Uniswap router to transfer tokens on behalf of this contract.
_grantUniswapRouterApprovalIfNecessary(token, tokenAmount);
// Establish path (direct or routed through Ether) from target token to Dai.
(address[] memory path, uint256[] memory amounts) = _createPathAndAmounts(
address(token), address(_DAI), routeThroughEther
);
// Trade the Dai for the quoted token amount on Uniswap and send to this contract.
amounts = _UNISWAP_ROUTER.swapExactTokensForTokens(
tokenAmount, quotedDaiAmount, path, address(this), deadline
);
totalDaiBought = amounts[path.length - 1];
// Transfer the quoted Dai amount to the caller and revert on failure.
_transferToken(_DAI, msg.sender, quotedDaiAmount);
_fireTradeEvent(
false, // not from reserves
TradeType.TOKEN_TO_DAI,
address(token),
tokenAmount,
quotedDaiAmount,
totalDaiBought.sub(quotedDaiAmount)
);
}
/**
* @notice Using `etherAmountFromReserves`, trade for Dai using Uniswap and
* use that Dai to mint Dharma Dai.
* Only the owner or the trade reserve role can call this function.
* @param etherAmountFromReserves the amount of Ether to take from reserves
* and add to the provided amount.
* @param quotedDaiAmount uint256 The amount of Dai requested in the trade.
* @param deadline uint256 The timestamp the order is valid until.
* @return The amount of Dai bought as part of the trade.
*/
function tradeEtherForDaiUsingReservesAndMintDDai(
uint256 etherAmountFromReserves, uint256 quotedDaiAmount, uint256 deadline
) external onlyOwnerOr(Role.RESERVE_TRADER) returns (
uint256 totalDaiBought, uint256 totalDDaiMinted
) {
// Trade the Ether for the quoted Dai amount on Uniswap.
totalDaiBought = _UNISWAP_DAI.ethToTokenSwapInput.value(
etherAmountFromReserves
)(
quotedDaiAmount, deadline
);
// Mint dDai using the received Dai.
totalDDaiMinted = _DDAI.mint(totalDaiBought);
_fireTradeEvent(
true, // from reserves
TradeType.ETH_TO_DAI,
address(0),
etherAmountFromReserves,
quotedDaiAmount,
totalDaiBought.sub(quotedDaiAmount)
);
}
/**
* @notice Using `etherAmountFromReserves`, trade for Dai using UniswapV2,
* and use that Dai to mint Dharma Dai.
* Only the owner or the trade reserve role can call this function.
* @param etherAmountFromReserves the amount of Ether to take from reserves
* and add to the provided amount.
* @param quotedDaiAmount uint256 The amount of Dai requested in the trade.
* @param deadline uint256 The timestamp the order is valid until.
* @return The amount of Dai bought as part of the trade.
*/
function tradeEtherForDaiUsingReservesAndMintDDaiV2(
uint256 etherAmountFromReserves, uint256 quotedDaiAmount, uint256 deadline
) external onlyOwnerOr(Role.RESERVE_TRADER) returns (
uint256 totalDaiBought, uint256 totalDDaiMinted
) {
// Establish path from Ether to Dai.
(address[] memory path, uint256[] memory amounts) = _createPathAndAmounts(
_WETH, address(_DAI), false
);
// Trade Ether for Dai on Uniswap (send to this contract).
amounts = _UNISWAP_ROUTER.swapExactETHForTokens.value(
etherAmountFromReserves
)(
quotedDaiAmount, path, address(this), deadline
);
totalDaiBought = amounts[1];
// Mint dDai using the received Dai.
totalDDaiMinted = _DDAI.mint(totalDaiBought);
_fireTradeEvent(
true, // from reserves
TradeType.ETH_TO_DAI,
address(0),
etherAmountFromReserves,
quotedDaiAmount,
totalDaiBought.sub(quotedDaiAmount)
);
}
function tradeEtherForTokenUsingReserves(
address token, uint256 etherAmountFromReserves, uint256 quotedTokenAmount, uint256 deadline
) external onlyOwnerOr(Role.RESERVE_TRADER) returns (uint256 totalEtherSold) {
// Establish path from Ether to target token.
(address[] memory path, uint256[] memory amounts) = _createPathAndAmounts(
_WETH, address(token), false
);
// Trade Ether for quoted token amount on Uniswap and send to this contract.
amounts = _UNISWAP_ROUTER.swapETHForExactTokens.value(etherAmountFromReserves)(
quotedTokenAmount, path, address(this), deadline
);
totalEtherSold = amounts[0];
_fireTradeEvent(
true, // from reserves
TradeType.ETH_TO_TOKEN,
address(token),
etherAmountFromReserves,
quotedTokenAmount,
etherAmountFromReserves.sub(totalEtherSold)
);
}
function tradeDaiForTokenUsingReserves(
address token, uint256 daiAmountFromReserves, uint256 quotedTokenAmount, uint256 deadline, bool routeThroughEther
) external onlyOwnerOr(Role.RESERVE_TRADER) returns (uint256 totalDaiSold) {
// Redeem dDai if the current Dai balance is less than is required.
_redeemDDaiIfNecessary(daiAmountFromReserves);
// Establish path (direct or routed through Ether) from Dai to target token.
(address[] memory path, uint256[] memory amounts) = _createPathAndAmounts(
address(_DAI), address(token), routeThroughEther
);
// Trade the Dai for the quoted token amount on Uniswap.
amounts = _UNISWAP_ROUTER.swapTokensForExactTokens(
quotedTokenAmount, daiAmountFromReserves, path, address(this), deadline
);
totalDaiSold = amounts[0];
_fireTradeEvent(
true, // from reserves
TradeType.DAI_TO_TOKEN,
address(token),
daiAmountFromReserves,
quotedTokenAmount,
daiAmountFromReserves.sub(totalDaiSold)
);
}
function tradeTokenForDaiUsingReservesAndMintDDai(
ERC20Interface token, uint256 tokenAmountFromReserves, uint256 quotedDaiAmount, uint256 deadline, bool routeThroughEther
) external onlyOwnerOr(Role.RESERVE_TRADER) returns (
uint256 totalDaiBought, uint256 totalDDaiMinted
) {
// Approve Uniswap router to transfer tokens on behalf of this contract.
_grantUniswapRouterApprovalIfNecessary(token, tokenAmountFromReserves);
// Establish path (direct or routed through Ether) from target token to Dai.
(address[] memory path, uint256[] memory amounts) = _createPathAndAmounts(
address(token), address(_DAI), routeThroughEther
);
// Trade the Dai for the quoted token amount on Uniswap.
amounts = _UNISWAP_ROUTER.swapExactTokensForTokens(
tokenAmountFromReserves, quotedDaiAmount, path, address(this), deadline
);
totalDaiBought = amounts[path.length - 1];
// Mint dDai using the received Dai.
totalDDaiMinted = _DDAI.mint(totalDaiBought);
_fireTradeEvent(
true, // from reserves
TradeType.TOKEN_TO_DAI,
address(token),
tokenAmountFromReserves,
quotedDaiAmount,
totalDaiBought.sub(quotedDaiAmount)
);
}
/**
* @notice Transfer `daiAmount` Dai to `smartWallet`, providing the initial
* user signing key `initialUserSigningKey` as proof that the specified smart
* wallet is indeed a Dharma Smart Wallet - this assumes that the address is
* derived and deployed using the Dharma Smart Wallet Factory V1. In addition,
* the specified amount must be less than the configured limit amount. Only
* the owner or the designated deposit manager role may call this function.
* @param smartWallet address The smart wallet to transfer Dai to.
* @param initialUserSigningKey address The initial user signing key supplied
* when deriving the smart wallet address - this could be an EOA or a Dharma
* key ring address.
* @param daiAmount uint256 The amount of Dai to transfer - this must be less
* than the current limit.
*/
function finalizeDaiDeposit(
address smartWallet, address initialUserSigningKey, uint256 daiAmount
) external onlyOwnerOr(Role.DEPOSIT_MANAGER) {
// Ensure that the recipient is indeed a smart wallet.
_ensureSmartWallet(smartWallet, initialUserSigningKey);
// Ensure that the amount to transfer is lower than the limit.
require(daiAmount < _daiLimit, "Transfer size exceeds the limit.");
// Transfer the Dai to the specified smart wallet.
_transferToken(_DAI, smartWallet, daiAmount);
}
/**
* @notice Transfer `dDaiAmount` Dharma Dai to `smartWallet`, providing the
* initial user signing key `initialUserSigningKey` as proof that the
* specified smart wallet is indeed a Dharma Smart Wallet - this assumes that
* the address is derived and deployed using the Dharma Smart Wallet Factory
* V1. In addition, the Dai equivalent value of the specified dDai amount must
* be less than the configured limit amount. Only the owner or the designated
* deposit manager role may call this function.
* @param smartWallet address The smart wallet to transfer Dai to.
* @param initialUserSigningKey address The initial user signing key supplied
* when deriving the smart wallet address - this could be an EOA or a Dharma
* key ring address.
* @param dDaiAmount uint256 The amount of Dharma Dai to transfer - the Dai
* equivalent amount must be less than the current limit.
*/
function finalizeDharmaDaiDeposit(
address smartWallet, address initialUserSigningKey, uint256 dDaiAmount
) external onlyOwnerOr(Role.DEPOSIT_MANAGER) {
// Ensure that the recipient is indeed a smart wallet.
_ensureSmartWallet(smartWallet, initialUserSigningKey);
// Get the current dDai exchange rate.
uint256 exchangeRate = _DDAI.exchangeRateCurrent();
// Ensure that an exchange rate was actually returned.
require(exchangeRate != 0, "Could not retrieve dDai exchange rate.");
// Get the equivalent Dai amount of the transfer.
uint256 daiEquivalent = (dDaiAmount.mul(exchangeRate)) / 1e18;
// Ensure that the amount to transfer is lower than the limit.
require(daiEquivalent < _daiLimit, "Transfer size exceeds the limit.");
// Transfer the dDai to the specified smart wallet.
_transferToken(ERC20Interface(address(_DDAI)), smartWallet, dDaiAmount);
}
/**
* @notice Transfer `etherAmount` Ether to `smartWallet`, providing the
* initial user signing key `initialUserSigningKey` as proof that the
* specified smart wallet is indeed a Dharma Smart Wallet - this assumes that
* the address is derived and deployed using the Dharma Smart Wallet Factory
* V1. In addition, the Ether amount must be less than the configured limit
* amount. Only the owner or the designated deposit manager role may call this
* function.
* @param smartWallet address The smart wallet to transfer Ether to.
* @param initialUserSigningKey address The initial user signing key supplied
* when deriving the smart wallet address - this could be an EOA or a Dharma
* key ring address.
* @param etherAmount uint256 The amount of Ether to transfer - this amount must be
* less than the current limit.
*/
function finalizeEtherDeposit(
address payable smartWallet,
address initialUserSigningKey,
uint256 etherAmount
) external onlyOwnerOr(Role.DEPOSIT_MANAGER) {
// Ensure that the recipient is indeed a smart wallet.
_ensureSmartWallet(smartWallet, initialUserSigningKey);
// Ensure that the amount to transfer is lower than the limit.
require(etherAmount < _etherLimit, "Transfer size exceeds the limit.");
// Transfer the Ether to the specified smart wallet.
bool ok;
(ok, ) = smartWallet.call.value(etherAmount)("");
if (!ok) {
assembly {
returndatacopy(0, 0, returndatasize)
revert(0, returndatasize)
}
}
}
/**
* @notice Use `daiAmount` Dai mint Dharma Dai. Only the owner or the
* designated adjuster role may call this function.
* @param daiAmount uint256 The amount of Dai to supply when minting Dharma
* Dai.
* @return The amount of Dharma Dai minted.
*/
function mint(
uint256 daiAmount
) external onlyOwnerOr(Role.ADJUSTER) returns (uint256 dDaiMinted) {
// Use the specified amount of Dai to mint dDai.
dDaiMinted = _DDAI.mint(daiAmount);
}
/**
* @notice Redeem `dDaiAmount` Dharma Dai for Dai. Only the owner or the
* designated adjuster role may call this function.
* @param dDaiAmount uint256 The amount of Dharma Dai to supply when redeeming
* for Dai.
* @return The amount of Dai received.
*/
function redeem(
uint256 dDaiAmount
) external onlyOwnerOr(Role.ADJUSTER) returns (uint256 daiReceived) {
// Redeem the specified amount of dDai for Dai.
daiReceived = _DDAI.redeem(dDaiAmount);
}
/**
* @notice trade `usdcAmount` USDC for Dharma Dai. Only the owner or the designated
* adjuster role may call this function.
* @param usdcAmount uint256 The amount of USDC to supply when trading for Dharma Dai.
* @param quotedDaiEquivalentAmount uint256 The expected DAI equivalent value of the
* received dDai - this value is returned from the `getAndExpectedDai` view function
* on the trade helper.
* @return The amount of dDai received.
*/
function tradeUSDCForDDai(
uint256 usdcAmount,
uint256 quotedDaiEquivalentAmount
) external onlyOwnerOr(Role.ADJUSTER) returns (uint256 dDaiMinted) {
dDaiMinted = _TRADE_HELPER.tradeUSDCForDDai(
usdcAmount, quotedDaiEquivalentAmount
);
}
/**
* @notice tradeDDaiForUSDC `daiEquivalentAmount` Dai amount to trade in Dharma Dai
* for USDC. Only the owner or the designated adjuster role may call this function.
* @param daiEquivalentAmount uint256 The Dai equivalent amount to supply in Dharma
* Dai when trading for USDC.
* @param quotedUSDCAmount uint256 The expected USDC received in exchange for
* dDai - this value is returned from the `getExpectedUSDC` view function on the
* trade helper.
* @return The amount of USDC received.
*/
function tradeDDaiForUSDC(
uint256 daiEquivalentAmount,
uint256 quotedUSDCAmount
) external onlyOwnerOr(Role.ADJUSTER) returns (uint256 usdcReceived) {
usdcReceived = _TRADE_HELPER.tradeDDaiForUSDC(
daiEquivalentAmount, quotedUSDCAmount
);
}
/**
* @notice Transfer `usdcAmount` USDC for to the current primary recipient set by the
* owner. Only the owner or the designated withdrawal manager role may call this function.
* @param usdcAmount uint256 The amount of USDC to transfer to the primary recipient.
*/
function withdrawUSDCToPrimaryRecipient(
uint256 usdcAmount
) external onlyOwnerOr(Role.WITHDRAWAL_MANAGER) {
// Get the current primary recipient.
address primaryRecipient = _primaryUSDCRecipient;
require(
primaryRecipient != address(0), "No USDC primary recipient currently set."
);
// Transfer the supplied USDC amount to the primary recipient.
_transferToken(_USDC, primaryRecipient, usdcAmount);
}
/**
* @notice Transfer `daiAmount` Dai for to the current primary recipient set by the
* owner. Only the owner or the designated withdrawal manager role may call this function.
* @param daiAmount uint256 The amount of Dai to transfer to the primary recipient.
*/
function withdrawDaiToPrimaryRecipient(
uint256 daiAmount
) external onlyOwnerOr(Role.WITHDRAWAL_MANAGER) {
// Get the current primary recipient.
address primaryRecipient = _primaryDaiRecipient;
require(
primaryRecipient != address(0), "No Dai primary recipient currently set."
);
// Transfer the supplied Dai amount to the primary recipient.
_transferToken(_DAI, primaryRecipient, daiAmount);
}
/**
* @notice Transfer `usdcAmount` USDC to `recipient`. Only the owner may call
* this function.
* @param recipient address The account to transfer USDC to.
* @param usdcAmount uint256 The amount of USDC to transfer.
*/
function withdrawUSDC(
address recipient, uint256 usdcAmount
) external onlyOwner {
// Transfer the USDC to the specified recipient.
_transferToken(_USDC, recipient, usdcAmount);
}
/**
* @notice Transfer `daiAmount` Dai to `recipient`. Only the owner may call
* this function.
* @param recipient address The account to transfer Dai to.
* @param daiAmount uint256 The amount of Dai to transfer.
*/
function withdrawDai(
address recipient, uint256 daiAmount
) external onlyOwner {
// Transfer the Dai to the specified recipient.
_transferToken(_DAI, recipient, daiAmount);
}
/**
* @notice Transfer `dDaiAmount` Dharma Dai to `recipient`. Only the owner may
* call this function.
* @param recipient address The account to transfer Dharma Dai to.
* @param dDaiAmount uint256 The amount of Dharma Dai to transfer.
*/
function withdrawDharmaDai(
address recipient, uint256 dDaiAmount
) external onlyOwner {
// Transfer the dDai to the specified recipient.
_transferToken(ERC20Interface(address(_DDAI)), recipient, dDaiAmount);
}
/**
* @notice Transfer `etherAmount` Ether to `recipient`. Only the owner may
* call this function.
* @param recipient address The account to transfer Ether to.
* @param etherAmount uint256 The amount of Ether to transfer.
*/
function withdrawEther(
address payable recipient, uint256 etherAmount
) external onlyOwner {
bool ok;
// Transfer the Ether to the specified recipient.
(ok, ) = recipient.call.value(etherAmount)("");
if (!ok) {
assembly {
returndatacopy(0, 0, returndatasize)
revert(0, returndatasize)
}
}
}
/**
* @notice Transfer `amount` of ERC20 token `token` to `recipient`. Only the
* owner may call this function.
* @param token ERC20Interface The ERC20 token to transfer.
* @param recipient address The account to transfer the tokens to.
* @param amount uint256 The amount of tokens to transfer.
* @return A boolean to indicate if the transfer was successful - note that
* unsuccessful ERC20 transfers will usually revert.
*/
function withdraw(
ERC20Interface token, address recipient, uint256 amount
) external onlyOwner returns (bool success) {
// Transfer the token to the specified recipient.
success = token.transfer(recipient, amount);
}
/**
* @notice Call account `target`, supplying value `amount` and data `data`.
* Only the owner may call this function.
* @param target address The account to call.
* @param amount uint256 The amount of ether to include as an endowment.
* @param data bytes The data to include along with the call.
* @return A boolean to indicate if the call was successful, as well as the
* returned data or revert reason.
*/
function callAny(
address payable target, uint256 amount, bytes calldata data
) external onlyOwner returns (bool ok, bytes memory returnData) {
// Call the specified target and supply the specified data.
(ok, returnData) = target.call.value(amount)(data);
}
/**
* @notice Set `daiAmount` as the new limit on the size of finalized deposits.
* Only the owner may call this function.
* @param daiAmount uint256 The new limit on the size of finalized deposits.
*/
function setDaiLimit(uint256 daiAmount) external onlyOwner {
// Set the new limit.
_daiLimit = daiAmount;
}
/**
* @notice Set `etherAmount` as the new limit on the size of finalized deposits.
* Only the owner may call this function.
* @param etherAmount uint256 The new limit on the size of finalized deposits.
*/
function setEtherLimit(uint256 etherAmount) external onlyOwner {
// Set the new limit.
_etherLimit = etherAmount;
}
/**
* @notice Set `recipient` as the new primary recipient for USDC withdrawals.
* Only the owner may call this function.
* @param recipient address The new primary recipient.
*/
function setPrimaryUSDCRecipient(address recipient) external onlyOwner {
// Set the new primary recipient.
_primaryUSDCRecipient = recipient;
}
/**
* @notice Set `recipient` as the new primary recipient for Dai withdrawals.
* Only the owner may call this function.
* @param recipient address The new primary recipient.
*/
function setPrimaryDaiRecipient(address recipient) external onlyOwner {
// Set the new primary recipient.
_primaryDaiRecipient = recipient;
}
/**
* @notice Pause a currently unpaused role and emit a `RolePaused` event. Only
* the owner or the designated pauser may call this function. Also, bear in
* mind that only the owner may unpause a role once paused.
* @param role The role to pause.
*/
function pause(Role role) external onlyOwnerOr(Role.PAUSER) {
RoleStatus storage storedRoleStatus = _roles[uint256(role)];
require(!storedRoleStatus.paused, "Role in question is already paused.");
storedRoleStatus.paused = true;
emit RolePaused(role);
}
/**
* @notice Unpause a currently paused role and emit a `RoleUnpaused` event.
* Only the owner may call this function.
* @param role The role to pause.
*/
function unpause(Role role) external onlyOwner {
RoleStatus storage storedRoleStatus = _roles[uint256(role)];
require(storedRoleStatus.paused, "Role in question is already unpaused.");
storedRoleStatus.paused = false;
emit RoleUnpaused(role);
}
/**
* @notice Set a new account on a given role and emit a `RoleModified` event
* if the role holder has changed. Only the owner may call this function.
* @param role The role that the account will be set for.
* @param account The account to set as the designated role bearer.
*/
function setRole(Role role, address account) external onlyOwner {
require(account != address(0), "Must supply an account.");
_setRole(role, account);
}
/**
* @notice Remove any current role bearer for a given role and emit a
* `RoleModified` event if a role holder was previously set. Only the owner
* may call this function.
* @param role The role that the account will be removed from.
*/
function removeRole(Role role) external onlyOwner {
_setRole(role, address(0));
}
/**
* @notice External view function to check whether or not the functionality
* associated with a given role is currently paused or not. The owner or the
* pauser may pause any given role (including the pauser itself), but only the
* owner may unpause functionality. Additionally, the owner may call paused
* functions directly.
* @param role The role to check the pause status on.
* @return A boolean to indicate if the functionality associated with the role
* in question is currently paused.
*/
function isPaused(Role role) external view returns (bool paused) {
paused = _isPaused(role);
}
/**
* @notice External view function to check whether the caller is the current
* role holder.
* @param role The role to check for.
* @return A boolean indicating if the caller has the specified role.
*/
function isRole(Role role) external view returns (bool hasRole) {
hasRole = _isRole(role);
}
/**
* @notice External view function to check whether a "proof" that a given
* smart wallet is actually a Dharma Smart Wallet, based on the initial user
* signing key, is valid or not. This proof only works when the Dharma Smart
* Wallet in question is derived using V1 of the Dharma Smart Wallet Factory.
* @param smartWallet address The smart wallet to check.
* @param initialUserSigningKey address The initial user signing key supplied
* when deriving the smart wallet address - this could be an EOA or a Dharma
* key ring address.
* @return A boolean indicating if the specified smart wallet account is
* indeed a smart wallet based on the specified initial user signing key.
*/
function isDharmaSmartWallet(
address smartWallet, address initialUserSigningKey
) external view returns (bool dharmaSmartWallet) {
dharmaSmartWallet = _isSmartWallet(smartWallet, initialUserSigningKey);
}
/**
* @notice External view function to check the account currently holding the
* deposit manager role. The deposit manager can process standard deposit
* finalization via `finalizeDaiDeposit` and `finalizeDharmaDaiDeposit`, but
* must prove that the recipient is a Dharma Smart Wallet and adhere to the
* current deposit size limit.
* @return The address of the current deposit manager, or the null address if
* none is set.
*/
function getDepositManager() external view returns (address depositManager) {
depositManager = _roles[uint256(Role.DEPOSIT_MANAGER)].account;
}
/**
* @notice External view function to check the account currently holding the
* adjuster role. The adjuster can exchange Dai in reserves for Dharma Dai and
* vice-versa via minting or redeeming.
* @return The address of the current adjuster, or the null address if none is
* set.
*/
function getAdjuster() external view returns (address adjuster) {
adjuster = _roles[uint256(Role.ADJUSTER)].account;
}
/**
* @notice External view function to check the account currently holding the
* reserve trader role. The reserve trader can trigger trades that utilize
* reserves in addition to supplied funds, if any.
* @return The address of the current reserve trader, or the null address if
* none is set.
*/
function getReserveTrader() external view returns (address reserveTrader) {
reserveTrader = _roles[uint256(Role.RESERVE_TRADER)].account;
}
/**
* @notice External view function to check the account currently holding the
* withdrawal manager role. The withdrawal manager can transfer USDC to the
* "primary recipient" address set by the owner.
* @return The address of the current withdrawal manager, or the null address
* if none is set.
*/
function getWithdrawalManager() external view returns (address withdrawalManager) {
withdrawalManager = _roles[uint256(Role.WITHDRAWAL_MANAGER)].account;
}
/**
* @notice External view function to check the account currently holding the
* pauser role. The pauser can pause any role from taking its standard action,
* though the owner will still be able to call the associated function in the
* interim and is the only entity able to unpause the given role once paused.
* @return The address of the current pauser, or the null address if none is
* set.
*/
function getPauser() external view returns (address pauser) {
pauser = _roles[uint256(Role.PAUSER)].account;
}
/**
* @notice External view function to check the current reserves held by this
* contract.
* @return The Dai and Dharma Dai reserves held by this contract, as well as
* the Dai-equivalent value of the Dharma Dai reserves.
*/
function getReserves() external view returns (
uint256 dai, uint256 dDai, uint256 dDaiUnderlying
) {
dai = _DAI.balanceOf(address(this));
dDai = _DDAI.balanceOf(address(this));
dDaiUnderlying = _DDAI.balanceOfUnderlying(address(this));
}
/**
* @notice External view function to check the current limit on deposit amount
* enforced for the deposit manager when finalizing deposits, expressed in Dai
* and in Dharma Dai.
* @return The Dai and Dharma Dai limit on deposit finalization amount.
*/
function getDaiLimit() external view returns (
uint256 daiAmount, uint256 dDaiAmount
) {
daiAmount = _daiLimit;
dDaiAmount = (daiAmount.mul(1e18)).div(_DDAI.exchangeRateCurrent());
}
/**
* @notice External view function to check the current limit on deposit amount
* enforced for the deposit manager when finalizing Ether deposits.
* @return The Ether limit on deposit finalization amount.
*/
function getEtherLimit() external view returns (uint256 etherAmount) {
etherAmount = _etherLimit;
}
/**
* @notice External view function to check the address of the current
* primary recipient for USDC.
* @return The primary recipient for USDC.
*/
function getPrimaryUSDCRecipient() external view returns (
address recipient
) {
recipient = _primaryUSDCRecipient;
}
/**
* @notice External view function to check the address of the current
* primary recipient for Dai.
* @return The primary recipient for Dai.
*/
function getPrimaryDaiRecipient() external view returns (
address recipient
) {
recipient = _primaryDaiRecipient;
}
function getImplementation() external view returns (
address implementation
) {
(bool ok, bytes memory returnData) = address(
0x2Cf7C0333D9b7F94BbF55B9701227E359F92fD31
).staticcall("");
require(ok && returnData.length == 32, "Invalid implementation.");
implementation = abi.decode(returnData, (address));
}
function getVersion() external view returns (uint256 version) {
version = _VERSION;
}
function _grantUniswapRouterApprovalIfNecessary(ERC20Interface token, uint256 amount) internal {
if (token.allowance(address(this), address(_UNISWAP_ROUTER)) < amount) {
// Remove approval for Uniswap router first as a workaround for unusual tokens.
(bool success, bytes memory data) = address(token).call(
abi.encodeWithSelector(
token.approve.selector, address(_UNISWAP_ROUTER), uint256(0)
)
);
require(
success && (data.length == 0 || abi.decode(data, (bool))),
"Token approval reset for Uniswap router failed."
);
// Grant approval for Uniswap router to transfer tokens on behalf of this contract.
(success, data) = address(token).call(
abi.encodeWithSelector(
token.approve.selector, address(_UNISWAP_ROUTER), uint256(-1)
)
);
require(
success && (data.length == 0 || abi.decode(data, (bool))),
"Token approval for Uniswap router failed."
);
}
}
/**
* @notice Internal function to set a new account on a given role and emit a
* `RoleModified` event if the role holder has changed.
* @param role The role that the account will be set for. Permitted roles are
* deposit manager (0), adjuster (1), and pauser (2).
* @param account The account to set as the designated role bearer.
*/
function _setRole(Role role, address account) internal {
RoleStatus storage storedRoleStatus = _roles[uint256(role)];
if (account != storedRoleStatus.account) {
storedRoleStatus.account = account;
emit RoleModified(role, account);
}
}
function _fireTradeEvent(
bool fromReserves,
TradeType tradeType,
address token,
uint256 suppliedAmount,
uint256 receivedAmount,
uint256 retainedAmount
) internal {
uint256 t = uint256(tradeType);
emit Trade(
fromReserves ? address(this) : msg.sender,
t < 2 ? address(_DAI) : (t % 2 == 0 ? address(0) : token),
(t > 1 && t < 4) ? address(_DAI) : (t % 2 == 0 ? token : address(0)),
t < 4 ? address(_DAI) : address(0),
suppliedAmount,
receivedAmount,
retainedAmount
);
}
/**
* @notice Internal view function to check whether the caller is the current
* role holder.
* @param role The role to check for.
* @return A boolean indicating if the caller has the specified role.
*/
function _isRole(Role role) internal view returns (bool hasRole) {
hasRole = msg.sender == _roles[uint256(role)].account;
}
/**
* @notice Internal view function to check whether the given role is paused or
* not.
* @param role The role to check for.
* @return A boolean indicating if the specified role is paused or not.
*/
function _isPaused(Role role) internal view returns (bool paused) {
paused = _roles[uint256(role)].paused;
}
/**
* @notice Internal view function to enforce that the given initial user signing
* key resolves to the given smart wallet when deployed through the Dharma Smart
* Wallet Factory V1. (staging version)
* @param smartWallet address The smart wallet.
* @param initialUserSigningKey address The initial user signing key.
*/
function _isSmartWallet(
address smartWallet, address initialUserSigningKey
) internal pure returns (bool) {
// Derive the keccak256 hash of the smart wallet initialization code.
bytes32 initCodeHash = keccak256(
abi.encodePacked(
_WALLET_CREATION_CODE_HEADER,
initialUserSigningKey,
_WALLET_CREATION_CODE_FOOTER
)
);
// Attempt to derive a smart wallet address that matches the one provided.
address target;
for (uint256 nonce = 0; nonce < 10; nonce++) {
target = address( // derive the target deployment address.
uint160( // downcast to match the address type.
uint256( // cast to uint to truncate upper digits.
keccak256( // compute CREATE2 hash using all inputs.
abi.encodePacked( // pack all inputs to the hash together.
_CREATE2_HEADER, // pass in control character + factory address.
nonce, // pass in current nonce as the salt.
initCodeHash // pass in hash of contract creation code.
)
)
)
)
);
// Exit early if the provided smart wallet matches derived target address.
if (target == smartWallet) {
return true;
}
// Otherwise, increment the nonce and derive a new salt.
nonce++;
}
// Explicity recognize no target was found matching provided smart wallet.
return false;
}
function _redeemDDaiIfNecessary(uint256 daiAmountFromReserves) internal {
uint256 daiBalance = _DAI.balanceOf(address(this));
if (daiBalance < daiAmountFromReserves) {
uint256 additionalDaiRequired = daiAmountFromReserves - daiBalance;
_DDAI.redeemUnderlying(additionalDaiRequired);
}
}
function _transferToken(ERC20Interface token, address to, uint256 amount) internal {
(bool success, bytes memory data) = address(token).call(
abi.encodeWithSelector(token.transfer.selector, to, amount)
);
require(
success && (data.length == 0 || abi.decode(data, (bool))),
'Transfer out failed.'
);
}
function _transferInToken(ERC20Interface token, address from, uint256 amount) internal {
(bool success, bytes memory data) = address(token).call(
abi.encodeWithSelector(token.transferFrom.selector, from, address(this), amount)
);
require(
success && (data.length == 0 || abi.decode(data, (bool))),
'Transfer in failed.'
);
}
function _ensureSmartWallet(
address smartWallet, address initialUserSigningKey
) internal pure {
require(
_isSmartWallet(smartWallet, initialUserSigningKey),
"Could not resolve smart wallet using provided signing key."
);
}
function _createPathAndAmounts(
address start, address end, bool routeThroughEther
) internal pure returns (address[] memory, uint256[] memory) {
uint256 pathLength = routeThroughEther ? 3 : 2;
address[] memory path = new address[](pathLength);
path[0] = start;
if (routeThroughEther) {
path[1] = _WETH;
}
path[pathLength - 1] = end;
return (path, new uint256[](pathLength));
}
/**
* @notice Modifier that throws if called by any account other than the owner
* or the supplied role, or if the caller is not the owner and the role in
* question is paused.
* @param role The role to require unless the caller is the owner.
*/
modifier onlyOwnerOr(Role role) {
if (!isOwner()) {
require(_isRole(role), "Caller does not have a required role.");
require(!_isPaused(role), "Role in question is currently paused.");
}
_;
}
}