Transaction Hash:
Block:
22215397 at Apr-07-2025 07:00:11 AM +UTC
Transaction Fee:
0.02114080305482726 ETH
$44.82
Gas Used:
215,359 Gas / 98.16540314 Gwei
Execution Trace
Coinbase 10.1a1da075( )
- ETH 0.69287828
0x7245e2a52a1d7759c875b323c0f3f20517d8878f.CALL( ) - ETH 0.0648912
0xba216dd93aa3d6ffb4348a5d5368d0f9afff6531.CALL( ) - ETH 0.6598002
0xcb36941b598c3201be8db7186e0d0c02fe1bb3ff.CALL( ) - ETH 0.096
0x402c3a7b0ab5d594df9030f11236f7c0b183036d.CALL( ) - ETH 0.02955624
0x5dee2e027ed4bd7609f1e4e1d36d7392a4f70948.CALL( ) - ETH 0.0697788
0xef7d46c7a7322e811608f7beec4386b186247cd3.CALL( ) - ETH 0.007666289223603696
0x6bc5e15c21cd60a06556eaa9b7672929609a09dd.CALL( ) - ETH 0.00665242675
0x26445110eb2a7ead0ab7c672577b113b07b61818.CALL( ) - ETH 0.26888395
0x12730824b9f08480e46fc8e64e2a28603064a090.CALL( ) ETH 0.009
0x2a07664aa89f229ca7b0933bd870228175648d49.CALL( )- ETH 0.009
SmartWallet.DELEGATECALL( )
- ETH 0.009
- ETH 0.0081835325
0x1eaaa865c6b2ebb3474e24eed6fa89a4434ff951.CALL( ) - ETH 0.00663711674971717
0xa850b2ade716017814d5cd8b9345eb4b33fece31.CALL( ) - ETH 0.75317039
0x10b6fdbbaef789e35bd57f295204e610aef819a0.CALL( )
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright 2017 Loopring Technology Limited.
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
import "../../lib/EIP712.sol";
import "../../lib/SignatureUtil.sol";
import "./GuardianLib.sol";
import "./WalletData.sol";
/// @title ApprovalLib
/// @dev Utility library for better handling of signed wallet requests.
/// This library must be deployed and linked to other modules.
///
/// @author Daniel Wang - <daniel@loopring.org>
library ApprovalLib {
using SignatureUtil for bytes32;
function verifyApproval(
Wallet storage wallet,
bytes32 domainSeparator,
SigRequirement sigRequirement,
Approval memory approval,
bytes memory encodedRequest
)
internal
returns (bytes32 approvedHash)
{
require(address(this) == approval.wallet, "INVALID_WALLET");
require(block.timestamp <= approval.validUntil, "EXPIRED_SIGNED_REQUEST");
approvedHash = EIP712.hashPacked(domainSeparator, keccak256(encodedRequest));
// Save hash to prevent replay attacks
require(!wallet.hashes[approvedHash], "HASH_EXIST");
wallet.hashes[approvedHash] = true;
require(
approvedHash.verifySignatures(approval.signers, approval.signatures),
"INVALID_SIGNATURES"
);
require(
GuardianLib.requireMajority(
wallet,
approval.signers,
sigRequirement
),
"PERMISSION_DENIED"
);
}
}
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright 2017 Loopring Technology Limited.
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
import "../../lib/SignatureUtil.sol";
import "./WalletData.sol";
/// @title ERC1271Lib
/// @author Brecht Devos - <brecht@loopring.org>
library ERC1271Lib
{
using SignatureUtil for bytes32;
// Note that we allow chained wallet ownership:
// Wallet1 owned by Wallet2, Wallet2 owned by Wallet3, ..., WaleltN owned by an EOA.
// The verificaiton of Wallet1's signature will succeed if the final EOA's signature is
// valid.
function isValidSignature(
Wallet storage wallet,
bytes4 ERC1271_MAGICVALUE,
bytes32 signHash,
bytes memory signature
)
public
view
returns (bytes4 magicValue)
{
if (wallet.locked) {
return 0;
}
if (signHash.verifySignature(wallet.owner, signature)) {
return ERC1271_MAGICVALUE;
} else {
return 0;
}
}
}
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright 2017 Loopring Technology Limited.
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
import "../../thirdparty/SafeERC20.sol";
import "../../lib/ERC20.sol";
import "../../lib/MathUint.sol";
import "../../lib/AddressUtil.sol";
import "../../iface/PriceOracle.sol";
import "../../thirdparty/BytesUtil.sol";
import "./WhitelistLib.sol";
import "./QuotaLib.sol";
import "./ApprovalLib.sol";
/// @title ERC20Lib
/// @author Brecht Devos - <brecht@loopring.org>
/// @author Daniel Wang - <daniel@loopring.org>
library ERC20Lib
{
using AddressUtil for address;
using BytesUtil for bytes;
using MathUint for uint;
using WhitelistLib for Wallet;
using QuotaLib for Wallet;
using ApprovalLib for Wallet;
using SafeERC20 for ERC20;
event Transfered (address token, address to, uint amount, bytes logdata);
event Approved (address token, address spender, uint amount);
event ContractCalled (address to, uint value, bytes data);
bytes32 public constant TRANSFER_TOKEN_TYPEHASH = keccak256(
"transferToken(address wallet,uint256 validUntil,address token,address to,uint256 amount,bytes logdata)"
);
bytes32 public constant APPROVE_TOKEN_TYPEHASH = keccak256(
"approveToken(address wallet,uint256 validUntil,address token,address to,uint256 amount)"
);
bytes32 public constant CALL_CONTRACT_TYPEHASH = keccak256(
"callContract(address wallet,uint256 validUntil,address to,uint256 value,bytes data)"
);
bytes32 public constant APPROVE_THEN_CALL_CONTRACT_TYPEHASH = keccak256(
"approveThenCallContract(address wallet,uint256 validUntil,address token,address to,uint256 amount,uint256 value,bytes data)"
);
function transferToken(
Wallet storage wallet,
PriceOracle priceOracle,
address token,
address to,
uint amount,
bytes calldata logdata,
bool forceUseQuota
)
external
{
if (forceUseQuota || !wallet.isAddressWhitelisted(to)) {
wallet.checkAndAddToSpent(priceOracle, token, amount);
}
_transferWithEvent(token, to, amount, logdata);
}
function transferTokenWA(
Wallet storage wallet,
bytes32 domainSeparator,
Approval calldata approval,
address token,
address to,
uint amount,
bytes calldata logdata
)
external
returns (bytes32 approvedHash)
{
approvedHash = wallet.verifyApproval(
domainSeparator,
SigRequirement.MAJORITY_OWNER_REQUIRED,
approval,
abi.encode(
TRANSFER_TOKEN_TYPEHASH,
approval.wallet,
approval.validUntil,
token,
to,
amount,
keccak256(logdata)
)
);
_transferWithEvent(token, to, amount, logdata);
}
function callContract(
Wallet storage wallet,
PriceOracle priceOracle,
address to,
uint value,
bytes calldata data,
bool forceUseQuota
)
external
returns (bytes memory returnData)
{
if (forceUseQuota || !wallet.isAddressWhitelisted(to)) {
wallet.checkAndAddToSpent(priceOracle, address(0), value);
}
return _callContractInternal(to, value, data, priceOracle);
}
function callContractWA(
Wallet storage wallet,
bytes32 domainSeparator,
Approval calldata approval,
address to,
uint value,
bytes calldata data
)
external
returns (bytes32 approvedHash, bytes memory returnData)
{
approvedHash = wallet.verifyApproval(
domainSeparator,
SigRequirement.MAJORITY_OWNER_REQUIRED,
approval,
abi.encode(
CALL_CONTRACT_TYPEHASH,
approval.wallet,
approval.validUntil,
to,
value,
keccak256(data)
)
);
returnData = _callContractInternal(to, value, data, PriceOracle(0));
}
function approveToken(
Wallet storage wallet,
PriceOracle priceOracle,
address token,
address to,
uint amount,
bool forceUseQuota
)
external
{
uint additionalAllowance = _approveInternal(token, to, amount);
if (forceUseQuota || !wallet.isAddressWhitelisted(to)) {
wallet.checkAndAddToSpent(priceOracle, token, additionalAllowance);
}
}
function approveTokenWA(
Wallet storage wallet,
bytes32 domainSeparator,
Approval calldata approval,
address token,
address to,
uint amount
)
external
returns (bytes32 approvedHash)
{
approvedHash = wallet.verifyApproval(
domainSeparator,
SigRequirement.MAJORITY_OWNER_REQUIRED,
approval,
abi.encode(
APPROVE_TOKEN_TYPEHASH,
approval.wallet,
approval.validUntil,
token,
to,
amount
)
);
_approveInternal(token, to, amount);
}
function approveThenCallContract(
Wallet storage wallet,
PriceOracle priceOracle,
address token,
address to,
uint amount,
uint value,
bytes calldata data,
bool forceUseQuota
)
external
returns (bytes memory returnData)
{
uint additionalAllowance = _approveInternal(token, to, amount);
if (forceUseQuota || !wallet.isAddressWhitelisted(to)) {
wallet.checkAndAddToSpent(priceOracle, token, additionalAllowance);
wallet.checkAndAddToSpent(priceOracle, address(0), value);
}
return _callContractInternal(to, value, data, priceOracle);
}
function approveThenCallContractWA(
Wallet storage wallet,
bytes32 domainSeparator,
Approval calldata approval,
address token,
address to,
uint amount,
uint value,
bytes calldata data
)
external
returns (bytes32 approvedHash, bytes memory returnData)
{
approvedHash = wallet.verifyApproval(
domainSeparator,
SigRequirement.MAJORITY_OWNER_REQUIRED,
approval,
abi.encode(
APPROVE_THEN_CALL_CONTRACT_TYPEHASH,
approval.wallet,
approval.validUntil,
token,
to,
amount,
value,
keccak256(data)
)
);
_approveInternal(token, to, amount);
returnData = _callContractInternal(to, value, data, PriceOracle(0));
}
function transfer(
address token,
address to,
uint amount
)
public
{
if (token == address(0)) {
to.sendETHAndVerify(amount, gasleft());
} else {
ERC20(token).safeTransfer(to, amount);
}
}
// --- Internal functions ---
function _transferWithEvent(
address token,
address to,
uint amount,
bytes calldata logdata
)
private
{
transfer(token, to, amount);
emit Transfered(token, to, amount, logdata);
}
function _approveInternal(
address token,
address spender,
uint amount
)
private
returns (uint additionalAllowance)
{
// Current allowance
uint allowance = ERC20(token).allowance(address(this), spender);
if (amount != allowance) {
// First reset the approved amount if needed
if (allowance > 0) {
ERC20(token).safeApprove(spender, 0);
}
// Now approve the requested amount
ERC20(token).safeApprove(spender, amount);
}
// If we increased the allowance, calculate by how much
if (amount > allowance) {
additionalAllowance = amount.sub(allowance);
}
emit Approved(token, spender, amount);
}
function _callContractInternal(
address to,
uint value,
bytes calldata txData,
PriceOracle priceOracle
)
private
returns (bytes memory returnData)
{
require(to != address(this), "SELF_CALL_DISALLOWED");
if (priceOracle != PriceOracle(0)) {
if (txData.length >= 4) {
bytes4 methodId = txData.toBytes4(0);
// bytes4(keccak256("transfer(address,uint256)")) = 0xa9059cbb
// bytes4(keccak256("approve(address,uint256)")) = 0x095ea7b3
if (methodId == bytes4(0xa9059cbb) ||
methodId == bytes4(0x095ea7b3)) {
// Disallow general calls to token contracts (for tokens that have price data
// so the quota is actually used).
require(priceOracle.tokenValue(to, 1e18) == 0, "CALL_DISALLOWED");
}
}
}
bool success;
(success, returnData) = to.call{value: value}(txData);
require(success, "CALL_FAILED");
emit ContractCalled(to, value, txData);
}
}
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright 2017 Loopring Technology Limited.
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
import "./WalletData.sol";
import "./ApprovalLib.sol";
import "../../lib/SignatureUtil.sol";
import "../../thirdparty/SafeCast.sol";
/// @title GuardianModule
/// @author Brecht Devos - <brecht@loopring.org>
/// @author Daniel Wang - <daniel@loopring.org>
library GuardianLib
{
using AddressUtil for address;
using SafeCast for uint;
using SignatureUtil for bytes32;
using ApprovalLib for Wallet;
uint public constant MAX_GUARDIANS = 10;
uint public constant GUARDIAN_PENDING_PERIOD = 3 days;
bytes32 public constant ADD_GUARDIAN_TYPEHASH = keccak256(
"addGuardian(address wallet,uint256 validUntil,address guardian)"
);
bytes32 public constant REMOVE_GUARDIAN_TYPEHASH = keccak256(
"removeGuardian(address wallet,uint256 validUntil,address guardian)"
);
bytes32 public constant RESET_GUARDIANS_TYPEHASH = keccak256(
"resetGuardians(address wallet,uint256 validUntil,address[] guardians)"
);
event GuardianAdded (address guardian, uint effectiveTime);
event GuardianRemoved (address guardian, uint effectiveTime);
function addGuardiansImmediately(
Wallet storage wallet,
address[] memory _guardians
)
external
{
address guardian = address(0);
for (uint i = 0; i < _guardians.length; i++) {
require(_guardians[i] > guardian, "INVALID_ORDERING");
guardian = _guardians[i];
_addGuardian(wallet, guardian, 0, true);
}
}
function addGuardian(
Wallet storage wallet,
address guardian
)
external
{
_addGuardian(wallet, guardian, GUARDIAN_PENDING_PERIOD, false);
}
function addGuardianWA(
Wallet storage wallet,
bytes32 domainSeparator,
Approval calldata approval,
address guardian
)
external
returns (bytes32 approvedHash)
{
approvedHash = wallet.verifyApproval(
domainSeparator,
SigRequirement.MAJORITY_OWNER_REQUIRED,
approval,
abi.encode(
ADD_GUARDIAN_TYPEHASH,
approval.wallet,
approval.validUntil,
guardian
)
);
_addGuardian(wallet, guardian, 0, true);
}
function removeGuardian(
Wallet storage wallet,
address guardian
)
external
{
_removeGuardian(wallet, guardian, GUARDIAN_PENDING_PERIOD, false);
}
function removeGuardianWA(
Wallet storage wallet,
bytes32 domainSeparator,
Approval calldata approval,
address guardian
)
external
returns (bytes32 approvedHash)
{
approvedHash = wallet.verifyApproval(
domainSeparator,
SigRequirement.MAJORITY_OWNER_REQUIRED,
approval,
abi.encode(
REMOVE_GUARDIAN_TYPEHASH,
approval.wallet,
approval.validUntil,
guardian
)
);
_removeGuardian(wallet, guardian, 0, true);
}
function resetGuardians(
Wallet storage wallet,
address[] calldata newGuardians
)
external
{
Guardian[] memory allGuardians = guardians(wallet, true);
for (uint i = 0; i < allGuardians.length; i++) {
_removeGuardian(wallet, allGuardians[i].addr, GUARDIAN_PENDING_PERIOD, false);
}
for (uint j = 0; j < newGuardians.length; j++) {
_addGuardian(wallet, newGuardians[j], GUARDIAN_PENDING_PERIOD, false);
}
}
function resetGuardiansWA(
Wallet storage wallet,
bytes32 domainSeparator,
Approval calldata approval,
address[] calldata newGuardians
)
external
returns (bytes32 approvedHash)
{
approvedHash = wallet.verifyApproval(
domainSeparator,
SigRequirement.MAJORITY_OWNER_REQUIRED,
approval,
abi.encode(
RESET_GUARDIANS_TYPEHASH,
approval.wallet,
approval.validUntil,
keccak256(abi.encodePacked(newGuardians))
)
);
removeAllGuardians(wallet);
for (uint i = 0; i < newGuardians.length; i++) {
_addGuardian(wallet, newGuardians[i], 0, true);
}
}
function requireMajority(
Wallet storage wallet,
address[] memory signers,
SigRequirement requirement
)
internal
view
returns (bool)
{
// We always need at least one signer
if (signers.length == 0) {
return false;
}
// Calculate total group sizes
Guardian[] memory allGuardians = guardians(wallet, false);
require(allGuardians.length > 0, "NO_GUARDIANS");
address lastSigner;
bool walletOwnerSigned = false;
address owner = wallet.owner;
for (uint i = 0; i < signers.length; i++) {
// Check for duplicates
require(signers[i] > lastSigner, "INVALID_SIGNERS_ORDER");
lastSigner = signers[i];
if (signers[i] == owner) {
walletOwnerSigned = true;
} else {
bool _isGuardian = false;
for (uint j = 0; j < allGuardians.length; j++) {
if (allGuardians[j].addr == signers[i]) {
_isGuardian = true;
break;
}
}
require(_isGuardian, "SIGNER_NOT_GUARDIAN");
}
}
if (requirement == SigRequirement.OWNER_OR_ANY_GUARDIAN) {
return signers.length == 1;
} else if (requirement == SigRequirement.ANY_GUARDIAN) {
require(!walletOwnerSigned, "WALLET_OWNER_SIGNATURE_NOT_ALLOWED");
return signers.length == 1;
}
// Check owner requirements
if (requirement == SigRequirement.MAJORITY_OWNER_REQUIRED) {
require(walletOwnerSigned, "WALLET_OWNER_SIGNATURE_REQUIRED");
} else if (requirement == SigRequirement.MAJORITY_OWNER_NOT_ALLOWED) {
require(!walletOwnerSigned, "WALLET_OWNER_SIGNATURE_NOT_ALLOWED");
}
uint numExtendedSigners = allGuardians.length;
if (walletOwnerSigned) {
numExtendedSigners += 1;
require(signers.length > 1, "NO_GUARDIAN_SIGNED_BESIDES_OWNER");
}
return signers.length >= (numExtendedSigners >> 1) + 1;
}
function isGuardian(
Wallet storage wallet,
address addr,
bool includePendingAddition
)
public
view
returns (bool)
{
Guardian memory g = _getGuardian(wallet, addr);
return _isActiveOrPendingAddition(g, includePendingAddition);
}
function guardians(
Wallet storage wallet,
bool includePendingAddition
)
public
view
returns (Guardian[] memory _guardians)
{
_guardians = new Guardian[](wallet.guardians.length);
uint index = 0;
for (uint i = 0; i < wallet.guardians.length; i++) {
Guardian memory g = wallet.guardians[i];
if (_isActiveOrPendingAddition(g, includePendingAddition)) {
_guardians[index] = g;
index++;
}
}
assembly { mstore(_guardians, index) }
}
function numGuardians(
Wallet storage wallet,
bool includePendingAddition
)
public
view
returns (uint count)
{
for (uint i = 0; i < wallet.guardians.length; i++) {
Guardian memory g = wallet.guardians[i];
if (_isActiveOrPendingAddition(g, includePendingAddition)) {
count++;
}
}
}
function removeAllGuardians(
Wallet storage wallet
)
internal
{
uint size = wallet.guardians.length;
if (size == 0) return;
for (uint i = 0; i < wallet.guardians.length; i++) {
delete wallet.guardianIdx[wallet.guardians[i].addr];
}
delete wallet.guardians;
}
function cancelPendingGuardians(Wallet storage wallet)
internal
{
bool cancelled = false;
for (uint i = 0; i < wallet.guardians.length; i++) {
Guardian memory g = wallet.guardians[i];
if (_isPendingAddition(g)) {
wallet.guardians[i].status = uint8(GuardianStatus.REMOVE);
wallet.guardians[i].timestamp = 0;
cancelled = true;
}
if (_isPendingRemoval(g)) {
wallet.guardians[i].status = uint8(GuardianStatus.ADD);
wallet.guardians[i].timestamp = 0;
cancelled = true;
}
}
_cleanRemovedGuardians(wallet, true);
}
function storeGuardian(
Wallet storage wallet,
address addr,
uint validSince,
bool alwaysOverride
)
internal
returns (uint)
{
require(validSince >= block.timestamp, "INVALID_VALID_SINCE");
require(addr != address(0), "ZERO_ADDRESS");
require(addr != address(this), "INVALID_ADDRESS");
uint pos = wallet.guardianIdx[addr];
if (pos == 0) {
// Add the new guardian
Guardian memory _g = Guardian(
addr,
uint8(GuardianStatus.ADD),
validSince.toUint64()
);
wallet.guardians.push(_g);
wallet.guardianIdx[addr] = wallet.guardians.length;
_cleanRemovedGuardians(wallet, false);
return validSince;
}
Guardian memory g = wallet.guardians[pos - 1];
if (_isRemoved(g)) {
wallet.guardians[pos - 1].status = uint8(GuardianStatus.ADD);
wallet.guardians[pos - 1].timestamp = validSince.toUint64();
return validSince;
}
if (_isPendingRemoval(g)) {
wallet.guardians[pos - 1].status = uint8(GuardianStatus.ADD);
wallet.guardians[pos - 1].timestamp = 0;
return 0;
}
if (_isPendingAddition(g)) {
if (!alwaysOverride) return g.timestamp;
wallet.guardians[pos - 1].timestamp = validSince.toUint64();
return validSince;
}
require(_isAdded(g), "UNEXPECTED_RESULT");
return 0;
}
function deleteGuardian(
Wallet storage wallet,
address addr,
uint validUntil,
bool alwaysOverride
)
internal
returns (uint)
{
require(validUntil >= block.timestamp, "INVALID_VALID_UNTIL");
require(addr != address(0), "ZERO_ADDRESS");
uint pos = wallet.guardianIdx[addr];
require(pos > 0, "GUARDIAN_NOT_EXISTS");
Guardian memory g = wallet.guardians[pos - 1];
if (_isAdded(g)) {
wallet.guardians[pos - 1].status = uint8(GuardianStatus.REMOVE);
wallet.guardians[pos - 1].timestamp = validUntil.toUint64();
return validUntil;
}
if (_isPendingAddition(g)) {
wallet.guardians[pos - 1].status = uint8(GuardianStatus.REMOVE);
wallet.guardians[pos - 1].timestamp = 0;
return 0;
}
if (_isPendingRemoval(g)) {
if (!alwaysOverride) return g.timestamp;
wallet.guardians[pos - 1].timestamp = validUntil.toUint64();
return validUntil;
}
require(_isRemoved(g), "UNEXPECTED_RESULT");
return 0;
}
// --- Internal functions ---
function _addGuardian(
Wallet storage wallet,
address guardian,
uint pendingPeriod,
bool alwaysOverride
)
internal
{
uint _numGuardians = numGuardians(wallet, true);
require(_numGuardians < MAX_GUARDIANS, "TOO_MANY_GUARDIANS");
require(guardian != wallet.owner, "GUARDIAN_CAN_NOT_BE_OWNER");
uint validSince = block.timestamp + 1;
if (_numGuardians >= 2) {
validSince = block.timestamp + pendingPeriod;
}
validSince = storeGuardian(wallet, guardian, validSince, alwaysOverride);
emit GuardianAdded(guardian, validSince);
}
function _removeGuardian(
Wallet storage wallet,
address guardian,
uint pendingPeriod,
bool alwaysOverride
)
private
{
uint validUntil = block.timestamp + pendingPeriod;
validUntil = deleteGuardian(wallet, guardian, validUntil, alwaysOverride);
emit GuardianRemoved(guardian, validUntil);
}
function _getGuardian(
Wallet storage wallet,
address addr
)
private
view
returns (Guardian memory guardian)
{
uint pos = wallet.guardianIdx[addr];
if (pos > 0) {
guardian = wallet.guardians[pos - 1];
}
}
function _isAdded(Guardian memory guardian)
private
view
returns (bool)
{
return guardian.status == uint8(GuardianStatus.ADD) &&
guardian.timestamp <= block.timestamp;
}
function _isPendingAddition(Guardian memory guardian)
private
view
returns (bool)
{
return guardian.status == uint8(GuardianStatus.ADD) &&
guardian.timestamp > block.timestamp;
}
function _isRemoved(Guardian memory guardian)
private
view
returns (bool)
{
return guardian.status == uint8(GuardianStatus.REMOVE) &&
guardian.timestamp <= block.timestamp;
}
function _isPendingRemoval(Guardian memory guardian)
private
view
returns (bool)
{
return guardian.status == uint8(GuardianStatus.REMOVE) &&
guardian.timestamp > block.timestamp;
}
function _isActive(Guardian memory guardian)
private
view
returns (bool)
{
return _isAdded(guardian) || _isPendingRemoval(guardian);
}
function _isActiveOrPendingAddition(
Guardian memory guardian,
bool includePendingAddition
)
private
view
returns (bool)
{
return _isActive(guardian) || includePendingAddition && _isPendingAddition(guardian);
}
function _cleanRemovedGuardians(
Wallet storage wallet,
bool force
)
private
{
uint count = wallet.guardians.length;
if (!force && count < 10) return;
for (int i = int(count) - 1; i >= 0; i--) {
Guardian memory g = wallet.guardians[uint(i)];
if (_isRemoved(g)) {
Guardian memory lastGuardian = wallet.guardians[wallet.guardians.length - 1];
if (g.addr != lastGuardian.addr) {
wallet.guardians[uint(i)] = lastGuardian;
wallet.guardianIdx[lastGuardian.addr] = uint(i) + 1;
}
wallet.guardians.pop();
delete wallet.guardianIdx[g.addr];
}
}
}
}
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright 2017 Loopring Technology Limited.
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
import "./WalletData.sol";
import "./GuardianLib.sol";
import "./LockLib.sol";
import "./Utils.sol";
/// @title InheritanceLib
/// @author Brecht Devos - <brecht@loopring.org>
library InheritanceLib
{
using GuardianLib for Wallet;
using InheritanceLib for Wallet;
using LockLib for Wallet;
using Utils for address;
// The minimal number of guardians for recovery and locking.
uint public constant TOUCH_GRACE_PERIOD = 30 days;
event Inherited(
address inheritor,
address newOwner
);
event InheritorChanged(
address inheritor,
uint32 waitingPeriod
);
function touchLastActiveWhenRequired(Wallet storage wallet)
internal
{
if (wallet.inheritor != address(0) &&
block.timestamp > wallet.lastActive + TOUCH_GRACE_PERIOD) {
wallet.lastActive = uint64(block.timestamp);
}
}
function setInheritor(
Wallet storage wallet,
address inheritor,
uint32 waitingPeriod
)
internal
{
if (inheritor == address(0)) {
require(waitingPeriod == 0, "INVALID_WAITING_PERIOD");
} else {
require(waitingPeriod >= TOUCH_GRACE_PERIOD, "INVALID_WAITING_PERIOD");
}
require(inheritor != address(this), "INVALID_ARGS");
wallet.inheritor = inheritor;
wallet.inheritWaitingPeriod = waitingPeriod;
wallet.lastActive = uint64(block.timestamp);
}
function inherit(
Wallet storage wallet,
address newOwner,
bool removeGuardians
)
external
{
require(wallet.inheritor == msg.sender, "UNAUTHORIZED");
require(wallet.owner != newOwner, "IS_WALLET_OWNER");
require(newOwner.isValidWalletOwner(), "INVALID_NEW_WALLET_OWNER");
require(uint(wallet.lastActive) + uint(wallet.inheritWaitingPeriod) < block.timestamp, "TOO_EARLY");
if (removeGuardians) {
wallet.removeAllGuardians();
}
wallet.setInheritor(address(0), 0);
wallet.setLock(address(this), false);
wallet.owner = newOwner;
emit Inherited(wallet.inheritor, newOwner);
}
}
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright 2017 Loopring Technology Limited.
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
import "./ApprovalLib.sol";
import "./WalletData.sol";
import "./GuardianLib.sol";
/// @title LockLib
/// @author Brecht Devos - <brecht@loopring.org>
library LockLib
{
using GuardianLib for Wallet;
using ApprovalLib for Wallet;
event WalletLocked (
address by,
bool locked
);
bytes32 public constant LOCK_TYPEHASH = keccak256(
"lock(address wallet,uint256 validUntil)"
);
bytes32 public constant UNLOCK_TYPEHASH = keccak256(
"unlock(address wallet,uint256 validUntil)"
);
function lock(Wallet storage wallet)
public
{
require(
msg.sender == address(this) ||
msg.sender == wallet.owner ||
wallet.isGuardian(msg.sender, false),
"NOT_FROM_WALLET_OR_OWNER_OR_GUARDIAN"
);
setLock(wallet, msg.sender, true);
}
function unlock(
Wallet storage wallet,
bytes32 domainSeparator,
Approval calldata approval
)
public
returns (bytes32 approvedHash)
{
approvedHash = wallet.verifyApproval(
domainSeparator,
SigRequirement.MAJORITY_OWNER_REQUIRED,
approval,
abi.encode(
UNLOCK_TYPEHASH,
approval.wallet,
approval.validUntil
)
);
setLock(wallet, msg.sender, false);
}
function setLock(
Wallet storage wallet,
address by,
bool locked
)
internal
{
wallet.locked = locked;
emit WalletLocked(by, locked);
}
}
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright 2017 Loopring Technology Limited.
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
import "../../lib/AddressUtil.sol";
import "../../lib/EIP712.sol";
import "../../lib/ERC20.sol";
import "../../lib/MathUint.sol";
import "../../lib/SignatureUtil.sol";
import "../../thirdparty/BytesUtil.sol";
import "./WalletData.sol";
import "./ERC20Lib.sol";
import "./QuotaLib.sol";
import "../SmartWallet.sol";
/// @title MetaTxLib
/// @dev A module to support wallet meta-transactions.
library MetaTxLib
{
using AddressUtil for address;
using BytesUtil for bytes;
using MathUint for uint;
using SignatureUtil for bytes32;
using QuotaLib for Wallet;
using ERC20Lib for Wallet;
bytes32 public constant META_TX_TYPEHASH = keccak256(
"MetaTx(address to,uint256 nonce,address gasToken,uint256 gasPrice,uint256 gasLimit,uint256 gasOverhead,address feeRecipient,bytes data,bytes32 approvedHash)"
);
event MetaTxExecuted(
uint nonce,
bytes32 approvedHash,
bytes32 metaTxHash,
bool success,
uint gasUsed
);
struct MetaTx
{
address to;
uint nonce;
address gasToken;
uint gasPrice;
uint gasLimit;
uint gasOverhead;
address feeRecipient;
bool requiresSuccess;
bytes data;
bytes signature;
}
function validateMetaTx(
Wallet storage wallet,
bytes32 DOMAIN_SEPARATOR,
MetaTx memory metaTx,
bool success,
bytes memory returnData
)
public
view
returns (bytes32)
{
// If this is a dataless meta-tx the user only signs the function selector,
// not the full function calldata.
bytes memory data = metaTx.nonce == 0 ? metaTx.data.slice(0, 4) : metaTx.data;
// Extracted the approved hash for dataless transactions
// The approved hash always needs to be the first value returned by the called function
// If the call failed we cannot deduce the approved hash so throw a nice
// error message here instead of failing in the signature check.
require(success || metaTx.nonce != 0, "APPROVED_HASH_UNKNOWN");
bytes32 approvedHash = metaTx.nonce == 0 ? returnData.toBytes32(0) : bytes32(0);
bytes32 encodedHash = keccak256(
abi.encode(
META_TX_TYPEHASH,
metaTx.to,
metaTx.nonce,
metaTx.gasToken,
metaTx.gasPrice,
metaTx.gasLimit,
metaTx.gasOverhead,
metaTx.feeRecipient,
metaTx.requiresSuccess,
keccak256(data),
approvedHash
)
);
bytes32 metaTxHash = EIP712.hashPacked(DOMAIN_SEPARATOR, encodedHash);
require(
metaTxHash.verifySignature(wallet.owner, metaTx.signature),
"METATX_INVALID_SIGNATURE"
);
return metaTxHash;
}
function executeMetaTx(
Wallet storage wallet,
bytes32 DOMAIN_SEPARATOR,
PriceOracle priceOracle,
MetaTx memory metaTx
)
public
returns (bool success)
{
uint gasLeft = gasleft();
require(gasLeft >= (metaTx.gasLimit.mul(64) / 63), "OPERATOR_INSUFFICIENT_GAS");
require(msg.sender != address(this), "RECURSIVE_METATXS_DISALLOWED");
// Only self calls allowed for now
require(metaTx.to == address(this));
// Update the nonce before the call to protect against reentrancy
require(isNonceValid(wallet, metaTx), "INVALID_NONCE");
if (metaTx.nonce != 0) {
wallet.nonce = metaTx.nonce;
}
// Do the actual call
bytes memory returnData;
(success, returnData) = metaTx.to.call{gas: metaTx.gasLimit}(metaTx.data);
// These checks are done afterwards to use the latest state post meta-tx call
require(!wallet.locked, "WALLET_LOCKED");
bytes32 metaTxHash = validateMetaTx(
wallet,
DOMAIN_SEPARATOR,
metaTx,
success,
returnData
);
uint gasUsed = gasLeft - gasleft() + metaTx.gasOverhead;
// Reimburse
if (metaTx.gasPrice > 0 && (!metaTx.requiresSuccess || success)) {
uint gasToReimburse = gasUsed <= metaTx.gasLimit ? gasUsed : metaTx.gasLimit;
uint gasCost = gasToReimburse.mul(metaTx.gasPrice);
wallet.checkAndAddToSpent(
priceOracle,
metaTx.gasToken,
gasCost
);
ERC20Lib.transfer(metaTx.gasToken, metaTx.feeRecipient, gasCost);
}
emit MetaTxExecuted(
metaTx.nonce,
metaTx.nonce == 0 ? returnData.toBytes32(0) : bytes32(0),
metaTxHash,
success,
gasUsed
);
}
function selfBatchCall(
Wallet storage /*wallet*/,
bytes[] calldata data
)
public
{
for (uint i = 0; i < data.length; i++) {
(bool success, ) = address(this).call(data[i]);
require(success, "BATCHED_CALL_FAILED");
}
}
function isNonceValid(
Wallet storage wallet,
MetaTx memory metaTx
)
public
view
returns (bool)
{
return (metaTx.nonce > wallet.nonce && (metaTx.nonce >> 128) <= block.number) ||
isDataless(metaTx);
}
function isDataless(
MetaTx memory metaTx
)
public
pure
returns (bool)
{
// We don't require any data in the meta tx when
// - the meta-tx has no nonce
// - the meta-tx needs to be successful
// - a function is called that requires a majority of guardians and fails when replayed
bytes4 methodId = metaTx.data.toBytes4(0);
return metaTx.nonce == 0 &&
metaTx.requiresSuccess &&
(methodId == SmartWallet.changeMasterCopy.selector ||
methodId == SmartWallet.addGuardianWA.selector ||
methodId == SmartWallet.removeGuardianWA.selector ||
methodId == SmartWallet.resetGuardiansWA.selector ||
methodId == SmartWallet.unlock.selector ||
methodId == SmartWallet.changeDailyQuotaWA.selector ||
methodId == SmartWallet.recover.selector ||
methodId == SmartWallet.addToWhitelistWA.selector ||
methodId == SmartWallet.transferTokenWA.selector ||
methodId == SmartWallet.callContractWA.selector ||
methodId == SmartWallet.approveTokenWA.selector ||
methodId == SmartWallet.approveThenCallContractWA.selector);
}
}
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright 2017 Loopring Technology Limited.
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
import "./ApprovalLib.sol";
import "./WalletData.sol";
import "../../iface/PriceOracle.sol";
import "../../lib/MathUint.sol";
import "../../thirdparty/SafeCast.sol";
/// @title QuotaLib
/// @dev This store maintains daily spending quota for each wallet.
/// A rolling daily limit is used.
library QuotaLib
{
using MathUint for uint;
using SafeCast for uint;
using ApprovalLib for Wallet;
uint128 public constant MAX_QUOTA = uint128(-1);
uint public constant QUOTA_PENDING_PERIOD = 1 days;
bytes32 public constant CHANGE_DAILY_QUOTE_TYPEHASH = keccak256(
"changeDailyQuota(address wallet,uint256 validUntil,uint256 newQuota)"
);
event QuotaScheduled(
address wallet,
uint pendingQuota,
uint64 pendingUntil
);
function changeDailyQuota(
Wallet storage wallet,
uint newQuota
)
public
{
setQuota(wallet, newQuota, block.timestamp.add(QUOTA_PENDING_PERIOD));
}
function changeDailyQuotaWA(
Wallet storage wallet,
bytes32 domainSeparator,
Approval calldata approval,
uint newQuota
)
public
returns (bytes32 approvedHash)
{
approvedHash = wallet.verifyApproval(
domainSeparator,
SigRequirement.MAJORITY_OWNER_REQUIRED,
approval,
abi.encode(
CHANGE_DAILY_QUOTE_TYPEHASH,
approval.wallet,
approval.validUntil,
newQuota
)
);
setQuota(wallet, newQuota, 0);
}
function checkAndAddToSpent(
Wallet storage wallet,
PriceOracle priceOracle,
address token,
uint amount
)
internal
{
Quota memory q = wallet.quota;
uint available = _availableQuota(q);
if (available != MAX_QUOTA) {
uint value = (token == address(0)) ?
amount :
((address(priceOracle) == address(0)) ?
0 :
priceOracle.tokenValue(token, amount));
if (value > 0) {
require(available >= value, "QUOTA_EXCEEDED");
_addToSpent(wallet, q, value);
}
}
}
// 0 for newQuota indicates unlimited quota, or daily quota is disabled.
function setQuota(
Wallet storage wallet,
uint newQuota,
uint effectiveTime
)
internal
{
require(newQuota <= MAX_QUOTA, "INVALID_VALUE");
if (newQuota == MAX_QUOTA) {
newQuota = 0;
}
uint __currentQuota = currentQuota(wallet);
// Always allow the quota to be changed immediately when the quota doesn't increase
if ((__currentQuota >= newQuota && newQuota != 0) || __currentQuota == 0) {
effectiveTime = 0;
}
Quota storage quota = wallet.quota;
quota.currentQuota = __currentQuota.toUint128();
quota.pendingQuota = newQuota.toUint128();
quota.pendingUntil = effectiveTime.toUint64();
emit QuotaScheduled(
address(this),
newQuota,
quota.pendingUntil
);
}
// Returns 0 to indiciate unlimited quota
function currentQuota(Wallet storage wallet)
internal
view
returns (uint)
{
return _currentQuota(wallet.quota);
}
// Returns 0 to indiciate unlimited quota
function pendingQuota(Wallet storage wallet)
internal
view
returns (
uint __pendingQuota,
uint __pendingUntil
)
{
return _pendingQuota(wallet.quota);
}
function spentQuota(Wallet storage wallet)
internal
view
returns (uint)
{
return _spentQuota(wallet.quota);
}
function availableQuota(Wallet storage wallet)
internal
view
returns (uint)
{
return _availableQuota(wallet.quota);
}
function hasEnoughQuota(
Wallet storage wallet,
uint requiredAmount
)
internal
view
returns (bool)
{
return _hasEnoughQuota(wallet.quota, requiredAmount);
}
// --- Internal functions ---
function _currentQuota(Quota memory q)
private
view
returns (uint)
{
return q.pendingUntil <= block.timestamp ? q.pendingQuota : q.currentQuota;
}
function _pendingQuota(Quota memory q)
private
view
returns (
uint __pendingQuota,
uint __pendingUntil
)
{
if (q.pendingUntil > 0 && q.pendingUntil > block.timestamp) {
__pendingQuota = q.pendingQuota;
__pendingUntil = q.pendingUntil;
}
}
function _spentQuota(Quota memory q)
private
view
returns (uint)
{
uint timeSinceLastSpent = block.timestamp.sub(q.spentTimestamp);
if (timeSinceLastSpent < 1 days) {
return uint(q.spentAmount).sub(timeSinceLastSpent.mul(q.spentAmount) / 1 days);
} else {
return 0;
}
}
function _availableQuota(Quota memory q)
private
view
returns (uint)
{
uint quota = _currentQuota(q);
if (quota == 0) {
return MAX_QUOTA;
}
uint spent = _spentQuota(q);
return quota > spent ? quota - spent : 0;
}
function _hasEnoughQuota(
Quota memory q,
uint requiredAmount
)
private
view
returns (bool)
{
return _availableQuota(q) >= requiredAmount;
}
function _addToSpent(
Wallet storage wallet,
Quota memory q,
uint amount
)
private
{
Quota storage s = wallet.quota;
s.spentAmount = _spentQuota(q).add(amount).toUint128();
s.spentTimestamp = uint64(block.timestamp);
}
}
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright 2017 Loopring Technology Limited.
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
import "./ApprovalLib.sol";
import "./WalletData.sol";
import "./GuardianLib.sol";
import "./LockLib.sol";
import "./Utils.sol";
/// @title RecoverLib
/// @author Brecht Devos - <brecht@loopring.org>
library RecoverLib
{
using GuardianLib for Wallet;
using LockLib for Wallet;
using ApprovalLib for Wallet;
using Utils for address;
event Recovered(address newOwner);
bytes32 public constant RECOVER_TYPEHASH = keccak256(
"recover(address wallet,uint256 validUntil,address newOwner,address[] newGuardians)"
);
/// @dev Recover a wallet by setting a new owner and guardians.
/// @param approval The approval.
/// @param newOwner The new owner address to set.
/// @param newGuardians The new guardians addresses to set.
function recover(
Wallet storage wallet,
bytes32 domainSeparator,
Approval calldata approval,
address newOwner,
address[] calldata newGuardians
)
external
returns (bytes32 approvedHash)
{
require(wallet.owner != newOwner, "IS_SAME_OWNER");
require(newOwner.isValidWalletOwner(), "INVALID_NEW_WALLET_OWNER");
approvedHash = wallet.verifyApproval(
domainSeparator,
SigRequirement.MAJORITY_OWNER_NOT_ALLOWED,
approval,
abi.encode(
RECOVER_TYPEHASH,
approval.wallet,
approval.validUntil,
newOwner,
keccak256(abi.encodePacked(newGuardians))
)
);
wallet.owner = newOwner;
wallet.setLock(address(this), false);
if (newGuardians.length > 0) {
for (uint i = 0; i < newGuardians.length; i++) {
require(newGuardians[i] != newOwner, "INVALID_NEW_WALLET_GUARDIAN");
}
wallet.removeAllGuardians();
wallet.addGuardiansImmediately(newGuardians);
} else {
if (wallet.isGuardian(newOwner, true)) {
wallet.deleteGuardian(newOwner, block.timestamp, true);
}
wallet.cancelPendingGuardians();
}
emit Recovered(newOwner);
}
}
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright 2017 Loopring Technology Limited.
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
import "./ApprovalLib.sol";
import "./WalletData.sol";
/// @title UpgradeLib
/// @author Brecht Devos - <brecht@loopring.org>
library UpgradeLib
{
using ApprovalLib for Wallet;
event ChangedMasterCopy (address masterCopy);
bytes32 public constant CHANGE_MASTER_COPY_TYPEHASH = keccak256(
"changeMasterCopy(address wallet,uint256 validUntil,address masterCopy)"
);
function changeMasterCopy(
Wallet storage wallet,
bytes32 domainSeparator,
Approval calldata approval,
address newMasterCopy
)
external
returns (bytes32 approvedHash)
{
require(newMasterCopy != address(0), "INVALID_MASTER_COPY");
approvedHash = wallet.verifyApproval(
domainSeparator,
SigRequirement.MAJORITY_OWNER_REQUIRED,
approval,
abi.encode(
CHANGE_MASTER_COPY_TYPEHASH,
approval.wallet,
approval.validUntil,
newMasterCopy
)
);
emit ChangedMasterCopy(newMasterCopy);
}
}
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright 2017 Loopring Technology Limited.
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
import "./WalletData.sol";
import "../../lib/AddressUtil.sol";
/// @title Utils
/// @author Brecht Devos - <brecht@loopring.org>
library Utils
{
using AddressUtil for address;
function isValidWalletOwner(address addr)
view
internal
returns (bool)
{
return addr != address(0) && !addr.isContract();
}
}
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright 2017 Loopring Technology Limited.
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
enum SigRequirement
{
MAJORITY_OWNER_NOT_ALLOWED,
MAJORITY_OWNER_ALLOWED,
MAJORITY_OWNER_REQUIRED,
OWNER_OR_ANY_GUARDIAN,
ANY_GUARDIAN
}
struct Approval
{
address[] signers;
bytes[] signatures;
uint validUntil;
address wallet;
}
// Optimized to fit into 64 bytes (2 slots)
struct Quota
{
uint128 currentQuota;
uint128 pendingQuota;
uint128 spentAmount;
uint64 spentTimestamp;
uint64 pendingUntil;
}
enum GuardianStatus
{
REMOVE, // Being removed or removed after validUntil timestamp
ADD // Being added or added after validSince timestamp.
}
// Optimized to fit into 32 bytes (1 slot)
struct Guardian
{
address addr;
uint8 status;
uint64 timestamp; // validSince if status = ADD; validUntil if adding = REMOVE;
}
struct Wallet
{
address owner;
uint64 creationTimestamp;
// relayer => nonce
uint nonce;
// hash => consumed
mapping (bytes32 => bool) hashes;
bool locked;
Guardian[] guardians;
mapping (address => uint) guardianIdx;
address inheritor;
uint32 inheritWaitingPeriod;
uint64 lastActive; // the latest timestamp the owner is considered to be active
Quota quota;
// whitelisted address => effective timestamp
mapping (address => uint) whitelisted;
}
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright 2017 Loopring Technology Limited.
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
import "./ApprovalLib.sol";
import "./WalletData.sol";
import "../../lib/MathUint.sol";
/// @title WhitelistLib
/// @dev This store maintains a wallet's whitelisted addresses.
library WhitelistLib
{
using MathUint for uint;
using WhitelistLib for Wallet;
using ApprovalLib for Wallet;
uint public constant WHITELIST_PENDING_PERIOD = 1 days;
bytes32 public constant ADD_TO_WHITELIST_TYPEHASH = keccak256(
"addToWhitelist(address wallet,uint256 validUntil,address addr)"
);
event Whitelisted(
address addr,
bool whitelisted,
uint effectiveTime
);
function addToWhitelist(
Wallet storage wallet,
address addr
)
external
{
wallet._addToWhitelist(
addr,
block.timestamp.add(WHITELIST_PENDING_PERIOD)
);
}
function addToWhitelistWA(
Wallet storage wallet,
bytes32 domainSeparator,
Approval calldata approval,
address addr
)
external
returns (bytes32 approvedHash)
{
approvedHash = wallet.verifyApproval(
domainSeparator,
SigRequirement.MAJORITY_OWNER_REQUIRED,
approval,
abi.encode(
ADD_TO_WHITELIST_TYPEHASH,
approval.wallet,
approval.validUntil,
addr
)
);
wallet._addToWhitelist(
addr,
block.timestamp
);
}
function removeFromWhitelist(
Wallet storage wallet,
address addr
)
external
{
wallet._removeFromWhitelist(addr);
}
function isAddressWhitelisted(
Wallet storage wallet,
address addr
)
internal
view
returns (bool)
{
uint effectiveTime = wallet.whitelisted[addr];
return effectiveTime > 0 && effectiveTime <= block.timestamp;
}
// --- Internal functions ---
function _addToWhitelist(
Wallet storage wallet,
address addr,
uint effectiveTime
)
internal
{
require(wallet.whitelisted[addr] == 0, "ADDRESS_ALREADY_WHITELISTED");
uint effective = effectiveTime >= block.timestamp ? effectiveTime : block.timestamp;
wallet.whitelisted[addr] = effective;
emit Whitelisted(addr, true, effective);
}
function _removeFromWhitelist(
Wallet storage wallet,
address addr
)
internal
{
delete wallet.whitelisted[addr];
emit Whitelisted(addr, false, 0);
}
}
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright 2017 Loopring Technology Limited.
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
import "../iface/ILoopringWalletV2.sol";
import "../lib/EIP712.sol";
import "../lib/ERC20.sol";
import "../lib/ERC1271.sol";
import "../lib/ReentrancyGuard.sol";
import "../thirdparty/erc165/IERC165.sol";
import "../thirdparty/erc1155/ERC1155Holder.sol";
import "../thirdparty/erc721/ERC721Holder.sol";
import "./libwallet/ERC20Lib.sol";
import "./libwallet/ERC1271Lib.sol";
import "./libwallet/WalletData.sol";
import "./libwallet/LockLib.sol";
import "./libwallet/GuardianLib.sol";
import "./libwallet/InheritanceLib.sol";
import "./libwallet/MetaTxLib.sol";
import "./libwallet/WhitelistLib.sol";
import "./libwallet/QuotaLib.sol";
import "./libwallet/RecoverLib.sol";
import "./libwallet/UpgradeLib.sol";
/// @title SmartWallet
/// @dev Main smart wallet contract
/// @author Brecht Devos - <brecht@loopring.org>
contract SmartWallet is ILoopringWalletV2, ERC1271, IERC165, ERC721Holder, ERC1155Holder
{
using ERC20Lib for Wallet;
using ERC1271Lib for Wallet;
using LockLib for Wallet;
using GuardianLib for Wallet;
using InheritanceLib for Wallet;
using MetaTxLib for Wallet;
using WhitelistLib for Wallet;
using QuotaLib for Wallet;
using RecoverLib for Wallet;
using UpgradeLib for Wallet;
bytes32 public immutable DOMAIN_SEPARATOR;
PriceOracle public immutable priceOracle;
address public immutable blankOwner;
// WARNING: Do not delete wallet state data to make this implementation
// compatible with early versions.
//
// ----- DATA LAYOUT BEGINS -----
// Always needs to be first
address internal masterCopy;
bool internal isImplementationContract;
Wallet public wallet;
// ----- DATA LAYOUT ENDS -----
/// @dev We need to make sure the implemenation contract cannot be initialized
/// and used to do delegate calls to arbitrary contracts.
modifier disableInImplementationContract
{
require(!isImplementationContract, "DISALLOWED_ON_IMPLEMENTATION_CONTRACT");
_;
}
modifier onlyFromWalletOrOwnerWhenUnlocked()
{
// If the wallet's signature verfication passes, the wallet must be unlocked.
require(
msg.sender == address(this) ||
(msg.sender == wallet.owner && !wallet.locked),
"NOT_FROM_WALLET_OR_OWNER_OR_WALLET_LOCKED"
);
wallet.touchLastActiveWhenRequired();
_;
}
modifier canTransferOwnership()
{
require(
msg.sender == blankOwner &&
wallet.owner == blankOwner,
"NOT_ALLOWED_TO_SET_OWNER"
);
_;
}
constructor(
PriceOracle _priceOracle,
address _blankOwner
)
{
isImplementationContract = true;
DOMAIN_SEPARATOR = EIP712.hash(
EIP712.Domain("LoopringWallet", "2.0.0", address(this))
);
priceOracle = _priceOracle;
blankOwner = _blankOwner;
}
/// @dev Set up this wallet.
///
/// Note that calling this method more than once will throw.
///
/// @param owner The owner of this wallet, must not be address(0).
/// @param guardians The guardians of this wallet.
function initialize(
address owner,
address[] calldata guardians,
uint quota,
address inheritor,
address feeRecipient,
address feeToken,
uint feeAmount
)
external
override
disableInImplementationContract
{
require(wallet.owner == address(0), "INITIALIZED_ALREADY");
require(owner != address(0), "INVALID_OWNER");
wallet.owner = owner;
wallet.creationTimestamp = uint64(block.timestamp);
wallet.addGuardiansImmediately(guardians);
if (quota != 0) {
wallet.setQuota(quota, 0);
}
if (inheritor != address(0)) {
wallet.setInheritor(inheritor, 365 days);
}
// Pay for the wallet creation using wallet funds
if (feeRecipient != address(0) && feeAmount > 0) {
ERC20Lib.transfer(feeToken, feeRecipient, feeAmount);
}
}
receive()
external
payable
{
}
function getOwner()
public
view
override
returns (address)
{
return wallet.owner;
}
function getCreationTimestamp()
public
view
override
returns (uint64)
{
return wallet.creationTimestamp;
}
//
// Owner
//
function transferOwnership(
address _owner
)
external
canTransferOwnership
{
require(_owner != address(0), "INVALID_OWNER");
wallet.owner = _owner;
}
//
// ERC1271
//
function isValidSignature(
bytes32 signHash,
bytes memory signature
)
public
view
override
returns (bytes4 magicValue)
{
return wallet.isValidSignature(
ERC1271_MAGICVALUE,
signHash,
signature
);
}
//
// Upgrade
//
function changeMasterCopy(
Approval calldata approval,
address newMasterCopy
)
external
returns (bytes32 approvedHash)
{
approvedHash = wallet.changeMasterCopy(
DOMAIN_SEPARATOR,
approval,
newMasterCopy
);
masterCopy = newMasterCopy;
}
function getMasterCopy()
public
view
returns (address)
{
return masterCopy;
}
//
// Guardians
//
function addGuardian(
address guardian
)
external
onlyFromWalletOrOwnerWhenUnlocked
{
wallet.addGuardian(guardian);
}
function addGuardianWA(
Approval calldata approval,
address guardian
)
external
returns (bytes32 approvedHash)
{
approvedHash = wallet.addGuardianWA(DOMAIN_SEPARATOR, approval, guardian);
}
function removeGuardian(
address guardian
)
external
onlyFromWalletOrOwnerWhenUnlocked
{
wallet.removeGuardian(guardian);
}
function removeGuardianWA(
Approval calldata approval,
address guardian
)
external
returns (bytes32 approvedHash)
{
approvedHash = wallet.removeGuardianWA(DOMAIN_SEPARATOR, approval, guardian);
}
function resetGuardians(
address[] calldata newGuardians
)
external
onlyFromWalletOrOwnerWhenUnlocked
{
wallet.resetGuardians(newGuardians);
}
function resetGuardiansWA(
Approval calldata approval,
address[] calldata newGuardians
)
external
returns (bytes32 approvedHash)
{
approvedHash = wallet.resetGuardiansWA(DOMAIN_SEPARATOR, approval, newGuardians);
}
function isGuardian(address addr, bool includePendingAddition)
public
view
returns (bool)
{
return wallet.isGuardian(addr, includePendingAddition);
}
function getGuardians(bool includePendingAddition)
public
view
returns (Guardian[] memory )
{
return GuardianLib.guardians(wallet, includePendingAddition);
}
//
// Inheritance
//
function setInheritor(
address inheritor,
uint32 waitingPeriod
)
external
onlyFromWalletOrOwnerWhenUnlocked
{
wallet.setInheritor(inheritor, waitingPeriod);
}
function inherit(
address newOwner,
bool removeGuardians
)
external
{
wallet.inherit(newOwner, removeGuardians);
}
//
// Lock
//
function lock()
external
{
wallet.lock();
}
function unlock(
Approval calldata approval
)
external
returns (bytes32 approvedHash)
{
approvedHash = wallet.unlock(DOMAIN_SEPARATOR, approval);
}
//
// Quota
//
function changeDailyQuota(
uint newQuota
)
external
onlyFromWalletOrOwnerWhenUnlocked
{
wallet.changeDailyQuota(newQuota);
}
function changeDailyQuotaWA(
Approval calldata approval,
uint newQuota
)
external
returns (bytes32 approvedHash)
{
approvedHash = wallet.changeDailyQuotaWA(DOMAIN_SEPARATOR, approval, newQuota);
}
//
// MetaTx
//
function executeMetaTx(
address to,
uint nonce,
address gasToken,
uint gasPrice,
uint gasLimit,
uint gasOverhead,
address feeRecipient,
bool requiresSuccess,
bytes calldata data,
bytes memory signature
)
external
returns (bool)
{
MetaTxLib.MetaTx memory metaTx = MetaTxLib.MetaTx(
to,
nonce,
gasToken,
gasPrice,
gasLimit,
gasOverhead,
feeRecipient,
requiresSuccess,
data,
signature
);
return wallet.executeMetaTx(
DOMAIN_SEPARATOR,
priceOracle,
metaTx
);
}
function selfBatchCall(
bytes[] calldata data
)
external
onlyFromWalletOrOwnerWhenUnlocked
{
wallet.selfBatchCall(data);
}
//
// Recover
//
function recover(
Approval calldata approval,
address newOwner,
address[] calldata newGuardians
)
external
returns (bytes32 approvedHash)
{
approvedHash = wallet.recover(
DOMAIN_SEPARATOR,
approval,
newOwner,
newGuardians
);
}
//
// Whitelist
//
function addToWhitelist(
address addr
)
external
onlyFromWalletOrOwnerWhenUnlocked
{
wallet.addToWhitelist(addr);
}
function addToWhitelistWA(
Approval calldata approval,
address addr
)
external
returns (bytes32 approvedHash)
{
approvedHash = wallet.addToWhitelistWA(
DOMAIN_SEPARATOR,
approval,
addr
);
}
function removeFromWhitelist(
address addr
)
external
onlyFromWalletOrOwnerWhenUnlocked
{
wallet.removeFromWhitelist(addr);
}
function getWhitelistEffectiveTime(
address addr
)
public
view
returns (uint)
{
return wallet.whitelisted[addr];
}
function isWhitelisted(
address addr
)
public
view
returns (bool) {
return wallet.isAddressWhitelisted(addr);
}
//
// ERC20
//
function transferToken(
address token,
address to,
uint amount,
bytes calldata logdata,
bool forceUseQuota
)
external
onlyFromWalletOrOwnerWhenUnlocked
{
wallet.transferToken(
priceOracle,
token,
to,
amount,
logdata,
forceUseQuota
);
}
function transferTokenWA(
Approval calldata approval,
address token,
address to,
uint amount,
bytes calldata logdata
)
external
returns (bytes32 approvedHash)
{
approvedHash = wallet.transferTokenWA(
DOMAIN_SEPARATOR,
approval,
token,
to,
amount,
logdata
);
}
function callContract(
address to,
uint value,
bytes calldata data,
bool forceUseQuota
)
external
onlyFromWalletOrOwnerWhenUnlocked
returns (bytes memory)
{
return wallet.callContract(
priceOracle,
to,
value,
data,
forceUseQuota
);
}
function callContractWA(
Approval calldata approval,
address to,
uint value,
bytes calldata data
)
external
returns (bytes32 approvedHash, bytes memory returnData)
{
(approvedHash, returnData) = wallet.callContractWA(
DOMAIN_SEPARATOR,
approval,
to,
value,
data
);
}
function approveToken(
address token,
address to,
uint amount,
bool forceUseQuota
)
external
onlyFromWalletOrOwnerWhenUnlocked
{
wallet.approveToken(
priceOracle,
token,
to,
amount,
forceUseQuota
);
}
function approveTokenWA(
Approval calldata approval,
address token,
address to,
uint amount
)
external
returns (bytes32 approvedHash)
{
approvedHash = wallet.approveTokenWA(
DOMAIN_SEPARATOR,
approval,
token,
to,
amount
);
}
function approveThenCallContract(
address token,
address to,
uint amount,
uint value,
bytes calldata data,
bool forceUseQuota
)
external
onlyFromWalletOrOwnerWhenUnlocked
returns (bytes memory)
{
return wallet.approveThenCallContract(
priceOracle,
token,
to,
amount,
value,
data,
forceUseQuota
);
}
function approveThenCallContractWA(
Approval calldata approval,
address token,
address to,
uint amount,
uint value,
bytes calldata data
)
external
returns (bytes32 approvedHash, bytes memory returnData)
{
(approvedHash, returnData) = wallet.approveThenCallContractWA(
DOMAIN_SEPARATOR,
approval,
token,
to,
amount,
value,
data
);
}
// ERC165
function supportsInterface(
bytes4 interfaceId
)
external
pure
override
returns (bool)
{
return interfaceId == type(ERC1271).interfaceId ||
interfaceId == type(IERC165).interfaceId ||
interfaceId == type(IERC721Receiver).interfaceId ||
interfaceId == type(IERC1155Receiver).interfaceId;
}
}
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright 2017 Loopring Technology Limited.
pragma solidity ^0.7.0;
/// @title Loopring SmartWallet V2 interface
/// @author Brecht Devos - <brecht@loopring.org>
abstract contract ILoopringWalletV2
{
/// @dev Initializes the smart wallet.
/// @param owner The wallet owner address.
/// @param guardians The initial wallet guardians.
/// @param quota The initial wallet quota.
/// @param inheritor The inheritor of the wallet.
/// @param feeRecipient The address receiving the fee for creating the wallet.
/// @param feeToken The token to use for the fee payment.
/// @param feeAmount The amount of tokens paid to the fee recipient.
function initialize(
address owner,
address[] calldata guardians,
uint quota,
address inheritor,
address feeRecipient,
address feeToken,
uint feeAmount
)
external
virtual;
/// @dev Returns the timestamp the wallet was created.
/// @return The timestamp the wallet was created.
function getCreationTimestamp()
public
view
virtual
returns (uint64);
/// @dev Returns the current wallet owner.
/// @return The current wallet owner.
function getOwner()
public
view
virtual
returns (address);
}
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright 2017 Loopring Technology Limited.
pragma solidity ^0.7.0;
/// @title PriceOracle
interface PriceOracle
{
// @dev Return's the token's value in ETH
function tokenValue(address token, uint amount)
external
view
returns (uint value);
}
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright 2017 Loopring Technology Limited.
pragma solidity ^0.7.0;
/// @title Utility Functions for addresses
/// @author Daniel Wang - <daniel@loopring.org>
/// @author Brecht Devos - <brecht@loopring.org>
library AddressUtil
{
using AddressUtil for *;
function isContract(
address addr
)
internal
view
returns (bool)
{
// According to EIP-1052, 0x0 is the value returned for not-yet created accounts
// and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
// for accounts without code, i.e. `keccak256('')`
bytes32 codehash;
// solhint-disable-next-line no-inline-assembly
assembly { codehash := extcodehash(addr) }
return (codehash != 0x0 &&
codehash != 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470);
}
function toPayable(
address addr
)
internal
pure
returns (address payable)
{
return payable(addr);
}
// Works like address.send but with a customizable gas limit
// Make sure your code is safe for reentrancy when using this function!
function sendETH(
address to,
uint amount,
uint gasLimit
)
internal
returns (bool success)
{
if (amount == 0) {
return true;
}
address payable recipient = to.toPayable();
/* solium-disable-next-line */
(success,) = recipient.call{value: amount, gas: gasLimit}("");
}
// Works like address.transfer but with a customizable gas limit
// Make sure your code is safe for reentrancy when using this function!
function sendETHAndVerify(
address to,
uint amount,
uint gasLimit
)
internal
returns (bool success)
{
success = to.sendETH(amount, gasLimit);
require(success, "TRANSFER_FAILURE");
}
// Works like call but is slightly more efficient when data
// needs to be copied from memory to do the call.
function fastCall(
address to,
uint gasLimit,
uint value,
bytes memory data
)
internal
returns (bool success, bytes memory returnData)
{
if (to != address(0)) {
assembly {
// Do the call
success := call(gasLimit, to, value, add(data, 32), mload(data), 0, 0)
// Copy the return data
let size := returndatasize()
returnData := mload(0x40)
mstore(returnData, size)
returndatacopy(add(returnData, 32), 0, size)
// Update free memory pointer
mstore(0x40, add(returnData, add(32, size)))
}
}
}
// Like fastCall, but throws when the call is unsuccessful.
function fastCallAndVerify(
address to,
uint gasLimit,
uint value,
bytes memory data
)
internal
returns (bytes memory returnData)
{
bool success;
(success, returnData) = fastCall(to, gasLimit, value, data);
if (!success) {
assembly {
revert(add(returnData, 32), mload(returnData))
}
}
}
}
// SPDX-License-Identifier: Apache-2.0
// Copyright 2017 Loopring Technology Limited.
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
library EIP712
{
struct Domain {
string name;
string version;
address verifyingContract;
}
bytes32 constant internal EIP712_DOMAIN_TYPEHASH = keccak256(
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
);
string constant internal EIP191_HEADER = "\\x19\\x01";
function hash(Domain memory domain)
internal
pure
returns (bytes32)
{
uint _chainid;
assembly { _chainid := chainid() }
return keccak256(
abi.encode(
EIP712_DOMAIN_TYPEHASH,
keccak256(bytes(domain.name)),
keccak256(bytes(domain.version)),
_chainid,
domain.verifyingContract
)
);
}
function hashPacked(
bytes32 domainSeparator,
bytes32 dataHash
)
internal
pure
returns (bytes32)
{
return keccak256(
abi.encodePacked(
EIP191_HEADER,
domainSeparator,
dataHash
)
);
}
}// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright 2017 Loopring Technology Limited.
pragma solidity ^0.7.0;
abstract contract ERC1271 {
// bytes4(keccak256("isValidSignature(bytes32,bytes)")
bytes4 constant internal ERC1271_MAGICVALUE = 0x1626ba7e;
function isValidSignature(
bytes32 _hash,
bytes memory _signature)
public
view
virtual
returns (bytes4 magicValue);
}
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright 2017 Loopring Technology Limited.
pragma solidity ^0.7.0;
/// @title ERC20 Token Interface
/// @dev see https://github.com/ethereum/EIPs/issues/20
/// @author Daniel Wang - <daniel@loopring.org>
abstract contract ERC20
{
function totalSupply()
public
view
virtual
returns (uint);
function balanceOf(
address who
)
public
view
virtual
returns (uint);
function allowance(
address owner,
address spender
)
public
view
virtual
returns (uint);
function transfer(
address to,
uint value
)
public
virtual
returns (bool);
function transferFrom(
address from,
address to,
uint value
)
public
virtual
returns (bool);
function approve(
address spender,
uint value
)
public
virtual
returns (bool);
}
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright 2017 Loopring Technology Limited.
pragma solidity ^0.7.0;
/// @title Utility Functions for uint
/// @author Daniel Wang - <daniel@loopring.org>
library MathUint
{
function mul(
uint a,
uint b
)
internal
pure
returns (uint c)
{
c = a * b;
require(a == 0 || c / a == b, "MUL_OVERFLOW");
}
function sub(
uint a,
uint b
)
internal
pure
returns (uint)
{
require(b <= a, "SUB_UNDERFLOW");
return a - b;
}
function add(
uint a,
uint b
)
internal
pure
returns (uint c)
{
c = a + b;
require(c >= a, "ADD_OVERFLOW");
}
}
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright 2017 Loopring Technology Limited.
pragma solidity ^0.7.0;
/// @title ReentrancyGuard
/// @author Brecht Devos - <brecht@loopring.org>
/// @dev Exposes a modifier that guards a function against reentrancy
/// Changing the value of the same storage value multiple times in a transaction
/// is cheap (starting from Istanbul) so there is no need to minimize
/// the number of times the value is changed
contract ReentrancyGuard
{
//The default value must be 0 in order to work behind a proxy.
uint private _guardValue;
modifier nonReentrant()
{
require(_guardValue == 0, "REENTRANCY");
_guardValue = 1;
_;
_guardValue = 0;
}
}
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright 2017 Loopring Technology Limited.
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
import "../thirdparty/BytesUtil.sol";
import "./AddressUtil.sol";
import "./ERC1271.sol";
import "./MathUint.sol";
/// @title SignatureUtil
/// @author Daniel Wang - <daniel@loopring.org>
/// @dev This method supports multihash standard. Each signature's last byte indicates
/// the signature's type.
library SignatureUtil
{
using BytesUtil for bytes;
using MathUint for uint;
using AddressUtil for address;
enum SignatureType {
ILLEGAL,
INVALID,
EIP_712,
ETH_SIGN,
WALLET // deprecated
}
bytes4 constant internal ERC1271_MAGICVALUE = 0x1626ba7e;
function verifySignatures(
bytes32 signHash,
address[] memory signers,
bytes[] memory signatures
)
internal
view
returns (bool)
{
require(signers.length == signatures.length, "BAD_SIGNATURE_DATA");
address lastSigner;
for (uint i = 0; i < signers.length; i++) {
require(signers[i] > lastSigner, "INVALID_SIGNERS_ORDER");
lastSigner = signers[i];
if (!verifySignature(signHash, signers[i], signatures[i])) {
return false;
}
}
return true;
}
function verifySignature(
bytes32 signHash,
address signer,
bytes memory signature
)
internal
view
returns (bool)
{
if (signer == address(0)) {
return false;
}
return signer.isContract()?
verifyERC1271Signature(signHash, signer, signature):
verifyEOASignature(signHash, signer, signature);
}
function recoverECDSASigner(
bytes32 signHash,
bytes memory signature
)
internal
pure
returns (address)
{
if (signature.length != 65) {
return address(0);
}
bytes32 r;
bytes32 s;
uint8 v;
// we jump 32 (0x20) as the first slot of bytes contains the length
// we jump 65 (0x41) per signature
// for v we load 32 bytes ending with v (the first 31 come from s) then apply a mask
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := and(mload(add(signature, 0x41)), 0xff)
}
// See https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/cryptography/ECDSA.sol
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return address(0);
}
if (v == 27 || v == 28) {
return ecrecover(signHash, v, r, s);
} else {
return address(0);
}
}
function verifyEOASignature(
bytes32 signHash,
address signer,
bytes memory signature
)
private
pure
returns (bool success)
{
if (signer == address(0)) {
return false;
}
\trequire(signature.length == 65 || signature.length == 66, "INVALID_SIGNATURE_LENGTH");
\tbool trimmed = false;
\tif (signature.length == 66) {
\t // Strip off the last byte of the signature by updating the length
\t assembly {
\t\tmstore(signature, 65)
\t }
\t trimmed = true;
\t}
\tsuccess = (signer == recoverECDSASigner(signHash, signature));
\tif (!success) {
bytes32 hash = keccak256(
abi.encodePacked("\\x19Ethereum Signed Message:\
32", signHash)
);
success = (signer == recoverECDSASigner(hash, signature));
}
\tif (trimmed) {
\t // Restore the signature length
\t assembly {
\t\tmstore(signature, 66)
\t }
\t}
}
function verifyERC1271Signature(
bytes32 signHash,
address signer,
bytes memory signature
)
private
view
returns (bool)
{
bytes memory callData = abi.encodeWithSelector(
ERC1271.isValidSignature.selector,
signHash,
signature
);
(bool success, bytes memory result) = signer.staticcall(callData);
return (
success &&
result.length == 32 &&
result.toBytes4(0) == ERC1271_MAGICVALUE
);
}
}
// SPDX-License-Identifier: MIT
// Token from: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/90ed1af972299070f51bf4665a85da56ac4d355e/contracts/utils/Address.sol
pragma solidity >=0.6.2 <0.8.0;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize, which returns 0 for contracts in
// construction, since the code is only stored at the end of the
// constructor execution.
uint256 size;
// solhint-disable-next-line no-inline-assembly
assembly { size := extcodesize(account) }
return size > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
// solhint-disable-next-line avoid-low-level-calls, avoid-call-value
(bool success, ) = recipient.call{ value: amount }("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain`call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
//require(address(this).balance >= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = target.call{ value: value }(data);
return _verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = target.staticcall(data);
return _verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.3._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.3._
*/
function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
require(isContract(target), "Address: delegate call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = target.delegatecall(data);
return _verifyCallResult(success, returndata, errorMessage);
}
function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
// solhint-disable-next-line no-inline-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}// SPDX-License-Identifier: UNLICENSED
// Taken from https://github.com/GNSPS/solidity-bytes-utils/blob/master/contracts/BytesLib.sol
pragma solidity ^0.7.0;
library BytesUtil {
function slice(
bytes memory _bytes,
uint _start,
uint _length
)
internal
pure
returns (bytes memory)
{
require(_bytes.length >= (_start + _length));
bytes memory tempBytes;
assembly {
switch iszero(_length)
case 0 {
// Get a location of some free memory and store it in tempBytes as
// Solidity does for memory variables.
tempBytes := mload(0x40)
// The first word of the slice result is potentially a partial
// word read from the original array. To read it, we calculate
// the length of that partial word and start copying that many
// bytes into the array. The first word we copy will start with
// data we don't care about, but the last `lengthmod` bytes will
// land at the beginning of the contents of the new array. When
// we're done copying, we overwrite the full first word with
// the actual length of the slice.
let lengthmod := and(_length, 31)
// The multiplication in the next line is necessary
// because when slicing multiples of 32 bytes (lengthmod == 0)
// the following copy loop was copying the origin's length
// and then ending prematurely not copying everything it should.
let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod)))
let end := add(mc, _length)
for {
// The multiplication in the next line has the same exact purpose
// as the one above.
let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start)
} lt(mc, end) {
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} {
mstore(mc, mload(cc))
}
mstore(tempBytes, _length)
//update free-memory pointer
//allocating the array padded to 32 bytes like the compiler does now
mstore(0x40, and(add(mc, 31), not(31)))
}
//if we want a zero-length slice let's just return a zero-length array
default {
tempBytes := mload(0x40)
mstore(0x40, add(tempBytes, 0x20))
}
}
return tempBytes;
}
function toAddress(bytes memory _bytes, uint _start) internal pure returns (address) {
require(_bytes.length >= (_start + 20));
address tempAddress;
assembly {
tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000)
}
return tempAddress;
}
function toUint8(bytes memory _bytes, uint _start) internal pure returns (uint8) {
require(_bytes.length >= (_start + 1));
uint8 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0x1), _start))
}
return tempUint;
}
function toUint16(bytes memory _bytes, uint _start) internal pure returns (uint16) {
require(_bytes.length >= (_start + 2));
uint16 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0x2), _start))
}
return tempUint;
}
function toUint24(bytes memory _bytes, uint _start) internal pure returns (uint24) {
require(_bytes.length >= (_start + 3));
uint24 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0x3), _start))
}
return tempUint;
}
function toUint32(bytes memory _bytes, uint _start) internal pure returns (uint32) {
require(_bytes.length >= (_start + 4));
uint32 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0x4), _start))
}
return tempUint;
}
function toUint64(bytes memory _bytes, uint _start) internal pure returns (uint64) {
require(_bytes.length >= (_start + 8));
uint64 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0x8), _start))
}
return tempUint;
}
function toUint96(bytes memory _bytes, uint _start) internal pure returns (uint96) {
require(_bytes.length >= (_start + 12));
uint96 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0xc), _start))
}
return tempUint;
}
function toUint128(bytes memory _bytes, uint _start) internal pure returns (uint128) {
require(_bytes.length >= (_start + 16));
uint128 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0x10), _start))
}
return tempUint;
}
function toUint(bytes memory _bytes, uint _start) internal pure returns (uint256) {
require(_bytes.length >= (_start + 32));
uint256 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0x20), _start))
}
return tempUint;
}
function toBytes4(bytes memory _bytes, uint _start) internal pure returns (bytes4) {
require(_bytes.length >= (_start + 4));
bytes4 tempBytes4;
assembly {
tempBytes4 := mload(add(add(_bytes, 0x20), _start))
}
return tempBytes4;
}
function toBytes32(bytes memory _bytes, uint _start) internal pure returns (bytes32) {
require(_bytes.length >= (_start + 32));
bytes32 tempBytes32;
assembly {
tempBytes32 := mload(add(add(_bytes, 0x20), _start))
}
return tempBytes32;
}
function fastSHA256(
bytes memory data
)
internal
view
returns (bytes32)
{
bytes32[] memory result = new bytes32[](1);
bool success;
assembly {
let ptr := add(data, 32)
success := staticcall(sub(gas(), 2000), 2, ptr, mload(data), add(result, 32), 32)
}
require(success, "SHA256_FAILED");
return result[0];
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
import "./IERC1155Receiver.sol";
/**
* @dev _Available since v3.1._
*/
contract ERC1155Holder is IERC1155Receiver {
function onERC1155Received(address, address, uint256, uint256, bytes memory) public virtual override pure returns (bytes4) {
return this.onERC1155Received.selector;
}
function onERC1155BatchReceived(address, address, uint256[] memory, uint256[] memory, bytes memory) public virtual override pure returns (bytes4) {
return this.onERC1155BatchReceived.selector;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
/**
* _Available since v3.1._
*/
interface IERC1155Receiver {
/**
@dev Handles the receipt of a single ERC1155 token type. This function is
called at the end of a `safeTransferFrom` after the balance has been updated.
To accept the transfer, this must return
`bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
(i.e. 0xf23a6e61, or its own function selector).
@param operator The address which initiated the transfer (i.e. msg.sender)
@param from The address which previously owned the token
@param id The ID of the token being transferred
@param value The amount of tokens being transferred
@param data Additional data with no specified format
@return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed
*/
function onERC1155Received(
address operator,
address from,
uint256 id,
uint256 value,
bytes calldata data
)
external
returns(bytes4);
/**
@dev Handles the receipt of a multiple ERC1155 token types. This function
is called at the end of a `safeBatchTransferFrom` after the balances have
been updated. To accept the transfer(s), this must return
`bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
(i.e. 0xbc197c81, or its own function selector).
@param operator The address which initiated the batch transfer (i.e. msg.sender)
@param from The address which previously owned the token
@param ids An array containing ids of each token being transferred (order and length must match values array)
@param values An array containing amounts of each token being transferred (order and length must match ids array)
@param data Additional data with no specified format
@return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed
*/
function onERC1155BatchReceived(
address operator,
address from,
uint256[] calldata ids,
uint256[] calldata values,
bytes calldata data
)
external
returns(bytes4);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
import "./IERC721Receiver.sol";
/**
* @dev Implementation of the {IERC721Receiver} interface.
*
* Accepts all token transfers.
* Make sure the contract is able to use its token with {IERC721-safeTransferFrom}, {IERC721-approve} or {IERC721-setApprovalForAll}.
*/
contract ERC721Holder is IERC721Receiver {
/**
* @dev See {IERC721Receiver-onERC721Received}.
*
* Always returns `IERC721Receiver.onERC721Received.selector`.
*/
function onERC721Received(address, address, uint256, bytes memory) public virtual override pure returns (bytes4) {
return this.onERC721Received.selector;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
/**
* @title ERC721 token receiver interface
* @dev Interface for any contract that wants to support safeTransfers
* from ERC721 asset contracts.
*/
interface IERC721Receiver {
/**
* @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
* by `operator` from `from`, this function is called.
*
* It must return its Solidity selector to confirm the token transfer.
* If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
*
* The selector can be obtained in Solidity with `IERC721.onERC721Received.selector`.
*/
function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) external returns (bytes4);
}
// SPDX-License-Identifier: MIT
// Taken from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/SafeCast.sol
pragma solidity ^0.7.0;
/**
* @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow
* checks.
*
* Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
* easily result in undesired exploitation or bugs, since developers usually
* assume that overflows raise errors. `SafeCast` restores this intuition by
* reverting the transaction when such an operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*
* Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing
* all math on `uint256` and `int256` and then downcasting.
*/
library SafeCast {
/**
* @dev Returns the downcasted uint128 from uint256, reverting on
* overflow (when the input is greater than largest uint128).
*
* Counterpart to Solidity's `uint128` operator.
*
* Requirements:
*
* - input must fit into 128 bits
*/
function toUint128(uint256 value) internal pure returns (uint128) {
require(value < 2**128, "SafeCast: value doesn\\'t fit in 128 bits");
return uint128(value);
}
/**
* @dev Returns the downcasted uint96 from uint256, reverting on
* overflow (when the input is greater than largest uint96).
*
* Counterpart to Solidity's `uint96` operator.
*
* Requirements:
*
* - input must fit into 96 bits
*/
function toUint96(uint256 value) internal pure returns (uint96) {
require(value < 2**96, "SafeCast: value doesn\\'t fit in 96 bits");
return uint96(value);
}
/**
* @dev Returns the downcasted uint64 from uint256, reverting on
* overflow (when the input is greater than largest uint64).
*
* Counterpart to Solidity's `uint64` operator.
*
* Requirements:
*
* - input must fit into 64 bits
*/
function toUint64(uint256 value) internal pure returns (uint64) {
require(value < 2**64, "SafeCast: value doesn\\'t fit in 64 bits");
return uint64(value);
}
/**
* @dev Returns the downcasted uint32 from uint256, reverting on
* overflow (when the input is greater than largest uint32).
*
* Counterpart to Solidity's `uint32` operator.
*
* Requirements:
*
* - input must fit into 32 bits
*/
function toUint32(uint256 value) internal pure returns (uint32) {
require(value < 2**32, "SafeCast: value doesn\\'t fit in 32 bits");
return uint32(value);
}
/**
* @dev Returns the downcasted uint40 from uint256, reverting on
* overflow (when the input is greater than largest uint40).
*
* Counterpart to Solidity's `uint32` operator.
*
* Requirements:
*
* - input must fit into 40 bits
*/
function toUint40(uint256 value) internal pure returns (uint40) {
require(value < 2**40, "SafeCast: value doesn\\'t fit in 40 bits");
return uint40(value);
}
/**
* @dev Returns the downcasted uint16 from uint256, reverting on
* overflow (when the input is greater than largest uint16).
*
* Counterpart to Solidity's `uint16` operator.
*
* Requirements:
*
* - input must fit into 16 bits
*/
function toUint16(uint256 value) internal pure returns (uint16) {
require(value < 2**16, "SafeCast: value doesn\\'t fit in 16 bits");
return uint16(value);
}
/**
* @dev Returns the downcasted uint8 from uint256, reverting on
* overflow (when the input is greater than largest uint8).
*
* Counterpart to Solidity's `uint8` operator.
*
* Requirements:
*
* - input must fit into 8 bits.
*/
function toUint8(uint256 value) internal pure returns (uint8) {
require(value < 2**8, "SafeCast: value doesn\\'t fit in 8 bits");
return uint8(value);
}
/**
* @dev Converts a signed int256 into an unsigned uint256.
*
* Requirements:
*
* - input must be greater than or equal to 0.
*/
function toUint256(int256 value) internal pure returns (uint256) {
require(value >= 0, "SafeCast: value must be positive");
return uint256(value);
}
/**
* @dev Returns the downcasted int128 from int256, reverting on
* overflow (when the input is less than smallest int128 or
* greater than largest int128).
*
* Counterpart to Solidity's `int128` operator.
*
* Requirements:
*
* - input must fit into 128 bits
*
* _Available since v3.1._
*/
function toInt128(int256 value) internal pure returns (int128) {
require(value >= -2**127 && value < 2**127, "SafeCast: value doesn\\'t fit in 128 bits");
return int128(value);
}
/**
* @dev Returns the downcasted int64 from int256, reverting on
* overflow (when the input is less than smallest int64 or
* greater than largest int64).
*
* Counterpart to Solidity's `int64` operator.
*
* Requirements:
*
* - input must fit into 64 bits
*
* _Available since v3.1._
*/
function toInt64(int256 value) internal pure returns (int64) {
require(value >= -2**63 && value < 2**63, "SafeCast: value doesn\\'t fit in 64 bits");
return int64(value);
}
/**
* @dev Returns the downcasted int32 from int256, reverting on
* overflow (when the input is less than smallest int32 or
* greater than largest int32).
*
* Counterpart to Solidity's `int32` operator.
*
* Requirements:
*
* - input must fit into 32 bits
*
* _Available since v3.1._
*/
function toInt32(int256 value) internal pure returns (int32) {
require(value >= -2**31 && value < 2**31, "SafeCast: value doesn\\'t fit in 32 bits");
return int32(value);
}
/**
* @dev Returns the downcasted int16 from int256, reverting on
* overflow (when the input is less than smallest int16 or
* greater than largest int16).
*
* Counterpart to Solidity's `int16` operator.
*
* Requirements:
*
* - input must fit into 16 bits
*
* _Available since v3.1._
*/
function toInt16(int256 value) internal pure returns (int16) {
require(value >= -2**15 && value < 2**15, "SafeCast: value doesn\\'t fit in 16 bits");
return int16(value);
}
/**
* @dev Returns the downcasted int8 from int256, reverting on
* overflow (when the input is less than smallest int8 or
* greater than largest int8).
*
* Counterpart to Solidity's `int8` operator.
*
* Requirements:
*
* - input must fit into 8 bits.
*
* _Available since v3.1._
*/
function toInt8(int256 value) internal pure returns (int8) {
require(value >= -2**7 && value < 2**7, "SafeCast: value doesn\\'t fit in 8 bits");
return int8(value);
}
/**
* @dev Converts an unsigned uint256 into a signed int256.
*
* Requirements:
*
* - input must be less than or equal to maxInt256.
*/
function toInt256(uint256 value) internal pure returns (int256) {
require(value < 2**255, "SafeCast: value doesn't fit in an int256");
return int256(value);
}
}// SPDX-License-Identifier: MIT
// Taken from: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/utils/SafeERC20.sol
pragma solidity >=0.6.0 <0.8.0;
import "./Address.sol";
import "../lib/ERC20.sol";
import "../lib/MathUint.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using MathUint for uint256;
using Address for address;
function safeTransfer(ERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(ERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(ERC20 token, address spender, uint256 value) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
// solhint-disable-next-line max-line-length
require((value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(ERC20 token, address spender, uint256 value) internal {
uint256 newAllowance = token.allowance(address(this), spender).add(value);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(ERC20 token, address spender, uint256 value) internal {
uint256 newAllowance = token.allowance(address(this), spender).sub(value);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(ERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) { // Return data is optional
// solhint-disable-next-line max-line-length
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}