Transaction Hash:
Block:
14501653 at Apr-01-2022 05:16:44 PM +UTC
Transaction Fee:
0.015284201422811796 ETH
$38.74
Gas Used:
106,086 Gas / 144.073689486 Gwei
Emitted Events:
174 |
Sand.Transfer( from=[Receiver] MultiGiveaway, to=[Sender] 0xc84ff195c904fcb2ee5ba8e70f8b84b2635a1bf2, value=1000000000000000000000 )
|
175 |
MultiGiveaway.ClaimedMultipleTokens( to=[Sender] 0xc84ff195c904fcb2ee5ba8e70f8b84b2635a1bf2, erc1155=, erc721=, erc20=[{name:amounts, type:uint256[], order:1, indexed:false, value:[1000000000000000000000], valueString:[1000000000000000000000]}, {name:contractAddresses, type:address[], order:2, indexed:false, value:[0x3845badAde8e6dFF049820680d1F14bD3903a5d0], valueString:[0x3845badAde8e6dFF049820680d1F14bD3903a5d0]}] )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x3845badA...D3903a5d0 | |||||
0xa21342f7...F8f83A9e4 | |||||
0xC84FF195...2635a1bF2 |
0.0294 Eth
Nonce: 0
|
0.014115798577188204 Eth
Nonce: 1
| 0.015284201422811796 | ||
0xEA674fdD...16B898ec8
Miner
| (Ethermine) | 911.56900231156334165 Eth | 911.56916144056334165 Eth | 0.000159129 |
Execution Trace
MultiGiveaway.claimMultipleTokens( )
-
Sand.transferFrom( from=0xa21342f796996954284B8DC6AAe7ecBF8f83A9e4, to=0xC84FF195c904fCB2Ee5ba8E70F8B84B2635a1bF2, amount=1000000000000000000000 ) => ( success=True )
claimMultipleTokens[MultiGiveaway (ln:60)]
_claimERC1155ERC721ERC20[MultiGiveaway (ln:72)]
File 1 of 2: MultiGiveaway
File 2 of 2: Sand
//SPDX-License-Identifier: MIT pragma solidity 0.8.2; import "@openzeppelin/contracts-0.8/token/ERC721/IERC721Receiver.sol"; import "@openzeppelin/contracts-0.8/token/ERC1155/IERC1155Receiver.sol"; import "./ClaimERC1155ERC721ERC20.sol"; import "../../common/BaseWithStorage/WithAdmin.sol"; /// @title MultiGiveaway contract. /// @notice This contract manages claims for multiple token types. contract MultiGiveaway is WithAdmin, ClaimERC1155ERC721ERC20 { /////////////////////////////// Data ////////////////////////////// bytes4 private constant ERC1155_RECEIVED = 0xf23a6e61; bytes4 private constant ERC1155_BATCH_RECEIVED = 0xbc197c81; bytes4 internal constant ERC721_RECEIVED = 0x150b7a02; bytes4 internal constant ERC721_BATCH_RECEIVED = 0x4b808c46; mapping(address => mapping(bytes32 => bool)) public claimed; mapping(bytes32 => uint256) internal _expiryTime; /////////////////////////////// Events ////////////////////////////// event NewGiveaway(bytes32 merkleRoot, uint256 expiryTime); /////////////////////////////// Constructor ///////////////////////// constructor(address admin) { _admin = admin; } /////////////////////////////// Functions /////////////////////////// /// @notice Function to add a new giveaway. /// @param merkleRoot The merkle root hash of the claim data. /// @param expiryTime The expiry time for the giveaway. function addNewGiveaway(bytes32 merkleRoot, uint256 expiryTime) external onlyAdmin { _expiryTime[merkleRoot] = expiryTime; emit NewGiveaway(merkleRoot, expiryTime); } /// @notice Function to check which giveaways have been claimed by a particular user. /// @param user The user (intended token destination) address. /// @param rootHashes The array of giveaway root hashes to check. /// @return claimedGiveaways The array of bools confirming whether or not the giveaways relating to the root hashes provided have been claimed. function getClaimedStatus(address user, bytes32[] calldata rootHashes) external view returns (bool[] memory) { bool[] memory claimedGiveaways = new bool[](rootHashes.length); for (uint256 i = 0; i < rootHashes.length; i++) { claimedGiveaways[i] = claimed[user][rootHashes[i]]; } return claimedGiveaways; } /// @notice Function to permit the claiming of multiple tokens from multiple giveaways to a reserved address. /// @param claims The array of claim structs, each containing a destination address, the giveaway items to be claimed and an optional salt param. /// @param proofs The proofs submitted for verification. function claimMultipleTokensFromMultipleMerkleTree( bytes32[] calldata rootHashes, Claim[] memory claims, bytes32[][] calldata proofs ) external { require(claims.length == rootHashes.length, "INVALID_INPUT"); require(claims.length == proofs.length, "INVALID_INPUT"); for (uint256 i = 0; i < rootHashes.length; i++) { claimMultipleTokens(rootHashes[i], claims[i], proofs[i]); } } /// @dev Public function used to perform validity checks and progress to claim multiple token types in one claim. /// @param merkleRoot The merkle root hash for the specific set of items being claimed. /// @param claim The claim struct containing the destination address, all items to be claimed and optional salt param. /// @param proof The proof provided by the user performing the claim function. function claimMultipleTokens( bytes32 merkleRoot, Claim memory claim, bytes32[] calldata proof ) public { uint256 giveawayExpiryTime = _expiryTime[merkleRoot]; require(claim.to != address(0), "INVALID_TO_ZERO_ADDRESS"); require(claim.to != address(this), "DESTINATION_MULTIGIVEAWAY_CONTRACT"); require(giveawayExpiryTime != 0, "GIVEAWAY_DOES_NOT_EXIST"); require(block.timestamp < giveawayExpiryTime, "CLAIM_PERIOD_IS_OVER"); require(claimed[claim.to][merkleRoot] == false, "DESTINATION_ALREADY_CLAIMED"); claimed[claim.to][merkleRoot] = true; _claimERC1155ERC721ERC20(merkleRoot, claim, proof); } function onERC721Received( address, /*operator*/ address, /*from*/ uint256, /*id*/ bytes calldata /*data*/ ) external pure returns (bytes4) { return ERC721_RECEIVED; } function onERC721BatchReceived( address, /*operator*/ address, /*from*/ uint256[] calldata, /*ids*/ bytes calldata /*data*/ ) external pure returns (bytes4) { return ERC721_BATCH_RECEIVED; } function onERC1155Received( address, /*operator*/ address, /*from*/ uint256, /*id*/ uint256, /*value*/ bytes calldata /*data*/ ) external pure returns (bytes4) { return ERC1155_RECEIVED; } function onERC1155BatchReceived( address, /*operator*/ address, /*from*/ uint256[] calldata, /*ids*/ uint256[] calldata, /*values*/ bytes calldata /*data*/ ) external pure returns (bytes4) { return ERC1155_BATCH_RECEIVED; } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.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 pragma solidity ^0.8.0; import "../../utils/introspection/IERC165.sol"; /** * _Available since v3.1._ */ interface IERC1155Receiver is IERC165 { /** @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.8.2; import "@openzeppelin/contracts-0.8/token/ERC1155/IERC1155.sol"; import "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts-0.8/token/ERC20/utils/SafeERC20.sol"; import "../../common/interfaces/IERC721Extended.sol"; import "../../common/Libraries/Verify.sol"; contract ClaimERC1155ERC721ERC20 { /////////////////////////////// Libs ////////////////////////////// using SafeERC20 for IERC20; /////////////////////////////// Data ////////////////////////////// struct Claim { address to; ERC1155Claim[] erc1155; ERC721Claim[] erc721; ERC20Claim erc20; bytes32 salt; } struct ERC1155Claim { uint256[] ids; uint256[] values; address contractAddress; } struct ERC721Claim { uint256[] ids; address contractAddress; } struct ERC20Claim { uint256[] amounts; address[] contractAddresses; } /////////////////////////////// Events ////////////////////////////// /// @dev Emits when a successful claim occurs. /// @param to The destination address for the claimed ERC1155, ERC721 and ERC20 tokens. /// @param erc1155 The array of ERC1155Claim structs containing the ids, values and ERC1155 contract address. /// @param erc721 The array of ERC721Claim structs containing the ids and ERC721 contract address. /// @param erc20 The ERC20Claim struct containing the amounts and ERC20 contract addresses. event ClaimedMultipleTokens(address to, ERC1155Claim[] erc1155, ERC721Claim[] erc721, ERC20Claim erc20); /////////////////////////////// Functions /////////////////////////// /// @dev Internal function used to claim multiple token types in one claim. /// @param merkleRoot The merkle root hash for the specific set of items being claimed. /// @param claim The claim struct containing the destination address, all items to be claimed and optional salt param. /// @param proof The proof provided by the user performing the claim function. function _claimERC1155ERC721ERC20( bytes32 merkleRoot, Claim memory claim, bytes32[] calldata proof ) internal { _checkValidity(merkleRoot, claim, proof); for (uint256 i = 0; i < claim.erc1155.length; i++) { require(claim.erc1155[i].ids.length == claim.erc1155[i].values.length, "INVALID_INPUT"); _transferERC1155(claim.to, claim.erc1155[i].ids, claim.erc1155[i].values, claim.erc1155[i].contractAddress); } for (uint256 i = 0; i < claim.erc721.length; i++) { _transferERC721(claim.to, claim.erc721[i].ids, claim.erc721[i].contractAddress); } if (claim.erc20.amounts.length != 0) { require(claim.erc20.amounts.length == claim.erc20.contractAddresses.length, "INVALID_INPUT"); _transferERC20(claim.to, claim.erc20.amounts, claim.erc20.contractAddresses); } emit ClaimedMultipleTokens(claim.to, claim.erc1155, claim.erc721, claim.erc20); } /// @dev Private function used to check the validity of a specific claim. /// @param merkleRoot The merkle root hash for the specific set of items being claimed. /// @param claim The claim struct containing the destination address, all items to be claimed and optional salt param. /// @param proof The proof provided by the user performing the claim function. function _checkValidity( bytes32 merkleRoot, Claim memory claim, bytes32[] memory proof ) private pure { bytes32 leaf = _generateClaimHash(claim); require(Verify.doesComputedHashMatchMerkleRootHash(merkleRoot, proof, leaf), "INVALID_CLAIM"); } /// @dev Private function used to generate a hash from an encoded claim. /// @param claim The claim struct. function _generateClaimHash(Claim memory claim) private pure returns (bytes32) { return keccak256(abi.encode(claim)); } /// @dev Private function used to transfer the ERC1155 tokens specified in a specific claim. /// @param to The destination address for the claimed tokens. /// @param ids The array of ERC1155 ids. /// @param values The amount of ERC1155 tokens of each id to be transferred. /// @param contractAddress The ERC1155 token contract address. function _transferERC1155( address to, uint256[] memory ids, uint256[] memory values, address contractAddress ) private { require(contractAddress != address(0), "INVALID_CONTRACT_ZERO_ADDRESS"); IERC1155(contractAddress).safeBatchTransferFrom(address(this), to, ids, values, ""); } /// @dev Private function used to transfer the ERC721tokens specified in a specific claim. /// @param to The destination address for the claimed tokens. /// @param ids The array of ERC721 ids. /// @param contractAddress The ERC721 token contract address. function _transferERC721( address to, uint256[] memory ids, address contractAddress ) private { require(contractAddress != address(0), "INVALID_CONTRACT_ZERO_ADDRESS"); IERC721Extended(contractAddress).safeBatchTransferFrom(address(this), to, ids, ""); } /// @dev Private function used to transfer the ERC20 tokens specified in a specific claim. /// @param to The destination address for the claimed tokens. /// @param amounts The array of amounts of ERC20 tokens to be transferred. /// @param contractAddresses The array of ERC20 token contract addresses. function _transferERC20( address to, uint256[] memory amounts, address[] memory contractAddresses ) private { for (uint256 i = 0; i < amounts.length; i++) { address erc20ContractAddress = contractAddresses[i]; uint256 erc20Amount = amounts[i]; require(erc20ContractAddress != address(0), "INVALID_CONTRACT_ZERO_ADDRESS"); IERC20(erc20ContractAddress).safeTransferFrom(address(this), to, erc20Amount); } } } //SPDX-License-Identifier: MIT // solhint-disable-next-line compiler-version pragma solidity 0.8.2; contract WithAdmin { address internal _admin; /// @dev Emits when the contract administrator is changed. /// @param oldAdmin The address of the previous administrator. /// @param newAdmin The address of the new administrator. event AdminChanged(address oldAdmin, address newAdmin); modifier onlyAdmin() { require(msg.sender == _admin, "ADMIN_ONLY"); _; } /// @dev Get the current administrator of this contract. /// @return The current administrator of this contract. function getAdmin() external view returns (address) { return _admin; } /// @dev Change the administrator to be `newAdmin`. /// @param newAdmin The address of the new administrator. function changeAdmin(address newAdmin) external { require(msg.sender == _admin, "ADMIN_ACCESS_DENIED"); emit AdminChanged(_admin, newAdmin); _admin = newAdmin; } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.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.8.0; import "../../utils/introspection/IERC165.sol"; /** * @dev Required interface of an ERC1155 compliant contract, as defined in the * https://eips.ethereum.org/EIPS/eip-1155[EIP]. * * _Available since v3.1._ */ interface IERC1155 is IERC165 { /** * @dev Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`. */ event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value); /** * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all * transfers. */ event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values); /** * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to * `approved`. */ event ApprovalForAll(address indexed account, address indexed operator, bool approved); /** * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI. * * If an {URI} event was emitted for `id`, the standard * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value * returned by {IERC1155MetadataURI-uri}. */ event URI(string value, uint256 indexed id); /** * @dev Returns the amount of tokens of token type `id` owned by `account`. * * Requirements: * * - `account` cannot be the zero address. */ function balanceOf(address account, uint256 id) external view returns (uint256); /** * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}. * * Requirements: * * - `accounts` and `ids` must have the same length. */ function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) external view returns (uint256[] memory); /** * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`, * * Emits an {ApprovalForAll} event. * * Requirements: * * - `operator` cannot be the caller. */ function setApprovalForAll(address operator, bool approved) external; /** * @dev Returns true if `operator` is approved to transfer ``account``'s tokens. * * See {setApprovalForAll}. */ function isApprovedForAll(address account, address operator) external view returns (bool); /** * @dev Transfers `amount` tokens of token type `id` from `from` to `to`. * * Emits a {TransferSingle} event. * * Requirements: * * - `to` cannot be the zero address. * - If the caller is not `from`, it must be have been approved to spend ``from``'s tokens via {setApprovalForAll}. * - `from` must have a balance of tokens of type `id` of at least `amount`. * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the * acceptance magic value. */ function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external; /** * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}. * * Emits a {TransferBatch} event. * * Requirements: * * - `ids` and `amounts` must have the same length. * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the * acceptance magic value. */ function safeBatchTransferFrom(address from, address to, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata data) external; } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IERC20 { /** * @dev Returns the amount of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the amount of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves `amount` tokens from the caller's account to `recipient`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address recipient, uint256 amount) external returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: Beware that changing an allowance with this method brings the risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. */ function approve(address spender, uint256 amount) external returns (bool); /** * @dev Moves `amount` tokens from `sender` to `recipient` using the * allowance mechanism. `amount` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); /** * @dev Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. */ event Transfer(address indexed from, address indexed to, uint256 value); /** * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ event Approval(address indexed owner, address indexed spender, uint256 value); } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "../IERC20.sol"; import "../../../utils/Address.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 Address for address; function safeTransfer(IERC20 token, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); } function safeTransferFrom(IERC20 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(IERC20 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(IERC20 token, address spender, uint256 value) internal { uint256 newAllowance = token.allowance(address(this), spender) + value; _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); } function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { unchecked { uint256 oldAllowance = token.allowance(address(this), spender); require(oldAllowance >= value, "SafeERC20: decreased allowance below zero"); uint256 newAllowance = oldAllowance - 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(IERC20 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"); } } } //SPDX-License-Identifier: MIT pragma solidity 0.8.2; import "@openzeppelin/contracts-0.8/token/ERC721/IERC721.sol"; interface IERC721Extended is IERC721 { function approveFor( address sender, address operator, uint256 id ) external; function batchTransferFrom( address from, address to, uint256[] calldata ids, bytes calldata data ) external; function safeBatchTransferFrom( address from, address to, uint256[] calldata ids, bytes calldata data ) external; function setApprovalForAllFor( address sender, address operator, bool approved ) external; function burn(uint256 id) external; function burnFrom(address from, uint256 id) external; } //SPDX-License-Identifier: MIT pragma solidity 0.8.2; /** * @title Verify * @dev Merkle root comparison function. */ library Verify { /// @dev Check if the computedHash == comparisonHash. /// @param comparisonHash The merkle root hash passed to the function. /// @param proof The proof provided by the user. /// @param leaf The generated hash. /// @return Whether the computedHash == comparisonHash. function doesComputedHashMatchMerkleRootHash( bytes32 comparisonHash, bytes32[] memory proof, bytes32 leaf ) internal pure returns (bool) { bytes32 computedHash = leaf; for (uint256 i = 0; i < proof.length; i++) { bytes32 proofElement = proof[i]; if (computedHash < proofElement) { computedHash = keccak256(abi.encodePacked(computedHash, proofElement)); } else { computedHash = keccak256(abi.encodePacked(proofElement, computedHash)); } } return computedHash == comparisonHash; } } // SPDX-License-Identifier: MIT pragma solidity ^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.4._ */ 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.4._ */ 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: MIT pragma solidity ^0.8.0; import "../../utils/introspection/IERC165.sol"; /** * @dev Required interface of an ERC721 compliant contract. */ interface IERC721 is IERC165 { /** * @dev Emitted when `tokenId` token is transferred from `from` to `to`. */ event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); /** * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token. */ event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); /** * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. */ event ApprovalForAll(address indexed owner, address indexed operator, bool approved); /** * @dev Returns the number of tokens in ``owner``'s account. */ function balanceOf(address owner) external view returns (uint256 balance); /** * @dev Returns the owner of the `tokenId` token. * * Requirements: * * - `tokenId` must exist. */ function ownerOf(uint256 tokenId) external view returns (address owner); /** * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients * are aware of the ERC721 protocol to prevent tokens from being forever locked. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must exist and be owned by `from`. * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}. * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. * * Emits a {Transfer} event. */ function safeTransferFrom(address from, address to, uint256 tokenId) external; /** * @dev Transfers `tokenId` token from `from` to `to`. * * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must be owned by `from`. * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. * * Emits a {Transfer} event. */ function transferFrom(address from, address to, uint256 tokenId) external; /** * @dev Gives permission to `to` to transfer `tokenId` token to another account. * The approval is cleared when the token is transferred. * * Only a single account can be approved at a time, so approving the zero address clears previous approvals. * * Requirements: * * - The caller must own the token or be an approved operator. * - `tokenId` must exist. * * Emits an {Approval} event. */ function approve(address to, uint256 tokenId) external; /** * @dev Returns the account approved for `tokenId` token. * * Requirements: * * - `tokenId` must exist. */ function getApproved(uint256 tokenId) external view returns (address operator); /** * @dev Approve or remove `operator` as an operator for the caller. * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller. * * Requirements: * * - The `operator` cannot be the caller. * * Emits an {ApprovalForAll} event. */ function setApprovalForAll(address operator, bool _approved) external; /** * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`. * * See {setApprovalForAll} */ function isApprovedForAll(address owner, address operator) external view returns (bool); /** * @dev Safely transfers `tokenId` token from `from` to `to`. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must exist and be owned by `from`. * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. * * Emits a {Transfer} event. */ function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; }
File 2 of 2: Sand
pragma solidity ^0.5.2; contract Admin { address internal _admin; event AdminChanged(address oldAdmin, address newAdmin); /// @notice gives the current administrator of this contract. /// @return the current administrator of this contract. function getAdmin() external view returns (address) { return _admin; } /// @notice change the administrator to be `newAdmin`. /// @param newAdmin address of the new administrator. function changeAdmin(address newAdmin) external { require(msg.sender == _admin, "only admin can change admin"); emit AdminChanged(_admin, newAdmin); _admin = newAdmin; } } pragma solidity ^0.5.2; import "./Admin.sol"; contract SuperOperators is Admin { mapping(address => bool) internal _superOperators; event SuperOperator(address superOperator, bool enabled); /// @notice Enable or disable the ability of `superOperator` to transfer tokens of all (superOperator rights). /// @param superOperator address that will be given/removed superOperator right. /// @param enabled set whether the superOperator is enabled or disabled. function setSuperOperator(address superOperator, bool enabled) external { require( msg.sender == _admin, "only admin is allowed to add super operators" ); _superOperators[superOperator] = enabled; emit SuperOperator(superOperator, enabled); } /// @notice check whether address `who` is given superOperator rights. /// @param who The address to query. /// @return whether the address has superOperator rights. function isSuperOperator(address who) public view returns (bool) { return _superOperators[who]; } } pragma solidity ^0.5.2; /* interface */ contract ERC20Events { event Transfer(address indexed from, address indexed to, uint256 value); event Approval( address indexed owner, address indexed spender, uint256 value ); } pragma solidity ^0.5.2; library BytesUtil { function memcpy(uint256 dest, uint256 src, uint256 len) internal pure { // Copy word-length chunks while possible for (; len >= 32; len -= 32) { assembly { mstore(dest, mload(src)) } dest += 32; src += 32; } // Copy remaining bytes uint256 mask = 256**(32 - len) - 1; assembly { let srcpart := and(mload(src), not(mask)) let destpart := and(mload(dest), mask) mstore(dest, or(destpart, srcpart)) } } function pointerToBytes(uint256 src, uint256 len) internal pure returns (bytes memory) { bytes memory ret = new bytes(len); uint256 retptr; assembly { retptr := add(ret, 32) } memcpy(retptr, src, len); return ret; } function addressToBytes(address a) internal pure returns (bytes memory b) { assembly { let m := mload(0x40) mstore( add(m, 20), xor(0x140000000000000000000000000000000000000000, a) ) mstore(0x40, add(m, 52)) b := m } } function uint256ToBytes(uint256 a) internal pure returns (bytes memory b) { assembly { let m := mload(0x40) mstore(add(m, 32), a) mstore(0x40, add(m, 64)) b := m } } function doFirstParamEqualsAddress(bytes memory data, address _address) internal pure returns (bool) { if (data.length < (36 + 32)) { return false; } uint256 value; assembly { value := mload(add(data, 36)) } return value == uint256(_address); } function doParamEqualsUInt256(bytes memory data, uint256 i, uint256 value) internal pure returns (bool) { if (data.length < (36 + (i + 1) * 32)) { return false; } uint256 offset = 36 + i * 32; uint256 valuePresent; assembly { valuePresent := mload(add(data, offset)) } return valuePresent == value; } function overrideFirst32BytesWithAddress( bytes memory data, address _address ) internal pure returns (bytes memory) { uint256 dest; assembly { dest := add(data, 48) } // 48 = 32 (offset) + 4 (func sig) + 12 (address is only 20 bytes) bytes memory addressBytes = addressToBytes(_address); uint256 src; assembly { src := add(addressBytes, 32) } memcpy(dest, src, 20); return data; } function overrideFirstTwo32BytesWithAddressAndInt( bytes memory data, address _address, uint256 _value ) internal pure returns (bytes memory) { uint256 dest; uint256 src; assembly { dest := add(data, 48) } // 48 = 32 (offset) + 4 (func sig) + 12 (address is only 20 bytes) bytes memory bbytes = addressToBytes(_address); assembly { src := add(bbytes, 32) } memcpy(dest, src, 20); assembly { dest := add(data, 68) } // 48 = 32 (offset) + 4 (func sig) + 32 (next slot) bbytes = uint256ToBytes(_value); assembly { src := add(bbytes, 32) } memcpy(dest, src, 32); return data; } } pragma solidity 0.5.9; import "./Sand/erc20/ERC20ExecuteExtension.sol"; import "./Sand/erc20/ERC20BaseToken.sol"; import "./Sand/erc20/ERC20BasicApproveExtension.sol"; contract Sand is ERC20ExecuteExtension, ERC20BasicApproveExtension, ERC20BaseToken { constructor(address sandAdmin, address executionAdmin, address beneficiary) public { _admin = sandAdmin; _executionAdmin = executionAdmin; _mint(beneficiary, 3000000000000000000000000000); } /// @notice A descriptive name for the tokens /// @return name of the tokens function name() public view returns (string memory) { return "SAND"; } /// @notice An abbreviated name for the tokens /// @return symbol of the tokens function symbol() public view returns (string memory) { return "SAND"; } } pragma solidity 0.5.9; import "../../../contracts_common/src/Interfaces/ERC20Events.sol"; import "../../../contracts_common/src/BaseWithStorage/SuperOperators.sol"; contract ERC20BaseToken is SuperOperators, ERC20Events { uint256 internal _totalSupply; mapping(address => uint256) internal _balances; mapping(address => mapping(address => uint256)) internal _allowances; /// @notice Gets the total number of tokens in existence. /// @return the total number of tokens in existence. function totalSupply() public view returns (uint256) { return _totalSupply; } /// @notice Gets the balance of `owner`. /// @param owner The address to query the balance of. /// @return The amount owned by `owner`. function balanceOf(address owner) public view returns (uint256) { return _balances[owner]; } /// @notice gets allowance of `spender` for `owner`'s tokens. /// @param owner address whose token is allowed. /// @param spender address allowed to transfer. /// @return the amount of token `spender` is allowed to transfer on behalf of `owner`. function allowance(address owner, address spender) public view returns (uint256 remaining) { return _allowances[owner][spender]; } /// @notice returns the number of decimals for that token. /// @return the number of decimals. function decimals() public view returns (uint8) { return uint8(18); } /// @notice Transfer `amount` tokens to `to`. /// @param to the recipient address of the tokens transfered. /// @param amount the number of tokens transfered. /// @return true if success. function transfer(address to, uint256 amount) public returns (bool success) { _transfer(msg.sender, to, amount); return true; } /// @notice Transfer `amount` tokens from `from` to `to`. /// @param from whose token it is transferring from. /// @param to the recipient address of the tokens transfered. /// @param amount the number of tokens transfered. /// @return true if success. function transferFrom(address from, address to, uint256 amount) public returns (bool success) { if (msg.sender != from && !_superOperators[msg.sender]) { uint256 currentAllowance = _allowances[from][msg.sender]; if (currentAllowance != (2**256) - 1) { // save gas when allowance is maximal by not reducing it (see https://github.com/ethereum/EIPs/issues/717) require(currentAllowance >= amount, "Not enough funds allowed"); _allowances[from][msg.sender] = currentAllowance - amount; } } _transfer(from, to, amount); return true; } /// @notice burn `amount` tokens. /// @param amount the number of tokens to burn. /// @return true if success. function burn(uint256 amount) external returns (bool) { _burn(msg.sender, amount); return true; } /// @notice burn `amount` tokens from `owner`. /// @param owner address whose token is to burn. /// @param amount the number of token to burn. /// @return true if success. function burnFor(address owner, uint256 amount) external returns (bool) { _burn(owner, amount); return true; } /// @notice approve `spender` to transfer `amount` tokens. /// @param spender address to be given rights to transfer. /// @param amount the number of tokens allowed. /// @return true if success. function approve(address spender, uint256 amount) public returns (bool success) { _approveFor(msg.sender, spender, amount); return true; } /// @notice approve `spender` to transfer `amount` tokens from `owner`. /// @param owner address whose token is allowed. /// @param spender address to be given rights to transfer. /// @param amount the number of tokens allowed. /// @return true if success. function approveFor(address owner, address spender, uint256 amount) public returns (bool success) { require( msg.sender == owner || _superOperators[msg.sender], "msg.sender != owner && !superOperator" ); _approveFor(owner, spender, amount); return true; } function addAllowanceIfNeeded(address owner, address spender, uint256 amountNeeded) public returns (bool success) { require( msg.sender == owner || _superOperators[msg.sender], "msg.sender != owner && !superOperator" ); _addAllowanceIfNeeded(owner, spender, amountNeeded); return true; } function _addAllowanceIfNeeded(address owner, address spender, uint256 amountNeeded) internal { if(amountNeeded > 0 && !isSuperOperator(spender)) { uint256 currentAllowance = _allowances[owner][spender]; if(currentAllowance < amountNeeded) { _approveFor(owner, spender, amountNeeded); } } } function _approveFor(address owner, address spender, uint256 amount) internal { require( owner != address(0) && spender != address(0), "Cannot approve with 0x0" ); _allowances[owner][spender] = amount; emit Approval(owner, spender, amount); } function _transfer(address from, address to, uint256 amount) internal { require(to != address(0), "Cannot send to 0x0"); uint256 currentBalance = _balances[from]; require(currentBalance >= amount, "not enough fund"); _balances[from] = currentBalance - amount; _balances[to] += amount; emit Transfer(from, to, amount); } function _mint(address to, uint256 amount) internal { require(to != address(0), "Cannot mint to 0x0"); require(amount > 0, "cannot mint 0 tokens"); uint256 currentTotalSupply = _totalSupply; uint256 newTotalSupply = currentTotalSupply + amount; require(newTotalSupply > currentTotalSupply, "overflow"); _totalSupply = newTotalSupply; _balances[to] += amount; emit Transfer(address(0), to, amount); } function _burn(address from, uint256 amount) internal { require(amount > 0, "cannot burn 0 tokens"); if (msg.sender != from && !_superOperators[msg.sender]) { uint256 currentAllowance = _allowances[from][msg.sender]; require( currentAllowance >= amount, "Not enough funds allowed" ); if (currentAllowance != (2**256) - 1) { // save gas when allowance is maximal by not reducing it (see https://github.com/ethereum/EIPs/issues/717) _allowances[from][msg.sender] = currentAllowance - amount; } } uint256 currentBalance = _balances[from]; require(currentBalance >= amount, "Not enough funds"); _balances[from] = currentBalance - amount; _totalSupply -= amount; emit Transfer(from, address(0), amount); } } pragma solidity 0.5.9; import "../../../contracts_common/src/Libraries/BytesUtil.sol"; contract ERC20BasicApproveExtension { /// @notice approve `target` to spend `amount` and call it with data. /// @param target address to be given rights to transfer and destination of the call. /// @param amount the number of tokens allowed. /// @param data bytes for the call. /// @return data of the call. function approveAndCall( address target, uint256 amount, bytes calldata data ) external payable returns (bytes memory) { require( BytesUtil.doFirstParamEqualsAddress(data, msg.sender), "first param != sender" ); _approveFor(msg.sender, target, amount); // solium-disable-next-line security/no-call-value (bool success, bytes memory returnData) = target.call.value(msg.value)(data); require(success, string(returnData)); return returnData; } /// @notice temporarly approve `target` to spend `amount` and call it with data. Previous approvals remains unchanged. /// @param target destination of the call, allowed to spend the amount specified /// @param amount the number of tokens allowed to spend. /// @param data bytes for the call. /// @return data of the call. function paidCall( address target, uint256 amount, bytes calldata data ) external payable returns (bytes memory) { require( BytesUtil.doFirstParamEqualsAddress(data, msg.sender), "first param != sender" ); if (amount > 0) { _addAllowanceIfNeeded(msg.sender, target, amount); } // solium-disable-next-line security/no-call-value (bool success, bytes memory returnData) = target.call.value(msg.value)(data); require(success, string(returnData)); return returnData; } function _approveFor(address owner, address target, uint256 amount) internal; function _addAllowanceIfNeeded(address owner, address spender, uint256 amountNeeded) internal; } pragma solidity 0.5.9; contract ERC20ExecuteExtension { /// @dev _executionAdmin != _admin so that this super power can be disabled independently address internal _executionAdmin; event ExecutionAdminAdminChanged(address oldAdmin, address newAdmin); /// @notice give the address responsible for adding execution rights. /// @return address of the execution administrator. function getExecutionAdmin() external view returns (address) { return _executionAdmin; } /// @notice change the execution adminstrator to be `newAdmin`. /// @param newAdmin address of the new administrator. function changeExecutionAdmin(address newAdmin) external { require(msg.sender == _executionAdmin, "only executionAdmin can change executionAdmin"); emit ExecutionAdminAdminChanged(_executionAdmin, newAdmin); _executionAdmin = newAdmin; } mapping(address => bool) internal _executionOperators; event ExecutionOperator(address executionOperator, bool enabled); /// @notice set `executionOperator` as executionOperator: `enabled`. /// @param executionOperator address that will be given/removed executionOperator right. /// @param enabled set whether the executionOperator is enabled or disabled. function setExecutionOperator(address executionOperator, bool enabled) external { require( msg.sender == _executionAdmin, "only execution admin is allowed to add execution operators" ); _executionOperators[executionOperator] = enabled; emit ExecutionOperator(executionOperator, enabled); } /// @notice check whether address `who` is given executionOperator rights. /// @param who The address to query. /// @return whether the address has executionOperator rights. function isExecutionOperator(address who) public view returns (bool) { return _executionOperators[who]; } /// @notice execute on behalf of the contract. /// @param to destination address fo the call. /// @param gasLimit exact amount of gas to be passed to the call. /// @param data the bytes sent to the destination address. /// @return success whether the execution was successful. /// @return returnData data resulting from the execution. function executeWithSpecificGas(address to, uint256 gasLimit, bytes calldata data) external returns (bool success, bytes memory returnData) { require(_executionOperators[msg.sender], "only execution operators allowed to execute on SAND behalf"); (success, returnData) = to.call.gas(gasLimit)(data); assert(gasleft() > gasLimit / 63); // not enough gas provided, assert to throw all gas // TODO use EIP-1930 } /// @notice approve a specific amount of token for `from` and execute on behalf of the contract. /// @param from address of which token will be transfered. /// @param to destination address fo the call. /// @param amount number of tokens allowed that can be transfer by the code at `to`. /// @param gasLimit exact amount of gas to be passed to the call. /// @param data the bytes sent to the destination address. /// @return success whether the execution was successful. /// @return returnData data resulting from the execution. function approveAndExecuteWithSpecificGas( address from, address to, uint256 amount, uint256 gasLimit, bytes calldata data ) external returns (bool success, bytes memory returnData) { require(_executionOperators[msg.sender], "only execution operators allowed to execute on SAND behalf"); return _approveAndExecuteWithSpecificGas(from, to, amount, gasLimit, data); } /// @dev the reason for this function is that charging for gas here is more gas-efficient than doing it in the caller. /// @notice approve a specific amount of token for `from` and execute on behalf of the contract. Plus charge the gas required to perform it. /// @param from address of which token will be transfered. /// @param to destination address fo the call. /// @param amount number of tokens allowed that can be transfer by the code at `to`. /// @param gasLimit exact amount of gas to be passed to the call. /// @param tokenGasPrice price in token for the gas to be charged. /// @param baseGasCharge amount of gas charged on top of the gas used for the call. /// @param tokenReceiver recipient address of the token charged for the gas used. /// @param data the bytes sent to the destination address. /// @return success whether the execution was successful. /// @return returnData data resulting from the execution. function approveAndExecuteWithSpecificGasAndChargeForIt( address from, address to, uint256 amount, uint256 gasLimit, uint256 tokenGasPrice, uint256 baseGasCharge, address tokenReceiver, bytes calldata data ) external returns (bool success, bytes memory returnData) { uint256 initialGas = gasleft(); require(_executionOperators[msg.sender], "only execution operators allowed to execute on SAND behalf"); (success, returnData) = _approveAndExecuteWithSpecificGas(from, to, amount, gasLimit, data); if (tokenGasPrice > 0) { _charge(from, gasLimit, tokenGasPrice, initialGas, baseGasCharge, tokenReceiver); } } /// @notice transfer 1amount1 token from `from` to `to` and charge the gas required to perform that transfer. /// @param from address of which token will be transfered. /// @param to destination address fo the call. /// @param amount number of tokens allowed that can be transfer by the code at `to`. /// @param gasLimit exact amount of gas to be passed to the call. /// @param tokenGasPrice price in token for the gas to be charged. /// @param baseGasCharge amount of gas charged on top of the gas used for the call. /// @param tokenReceiver recipient address of the token charged for the gas used. /// @return whether the transfer was successful. function transferAndChargeForGas( address from, address to, uint256 amount, uint256 gasLimit, uint256 tokenGasPrice, uint256 baseGasCharge, address tokenReceiver ) external returns (bool) { uint256 initialGas = gasleft(); require(_executionOperators[msg.sender], "only execution operators allowed to perfrom transfer and charge"); _transfer(from, to, amount); if (tokenGasPrice > 0) { _charge(from, gasLimit, tokenGasPrice, initialGas, baseGasCharge, tokenReceiver); } return true; } function _charge( address from, uint256 gasLimit, uint256 tokenGasPrice, uint256 initialGas, uint256 baseGasCharge, address tokenReceiver ) internal { uint256 gasCharge = initialGas - gasleft(); if(gasCharge > gasLimit) { gasCharge = gasLimit; } gasCharge += baseGasCharge; uint256 tokensToCharge = gasCharge * tokenGasPrice; require(tokensToCharge / gasCharge == tokenGasPrice, "overflow"); _transfer(from, tokenReceiver, tokensToCharge); } function _approveAndExecuteWithSpecificGas( address from, address to, uint256 amount, uint256 gasLimit, bytes memory data ) internal returns (bool success, bytes memory returnData) { if (amount > 0) { _addAllowanceIfNeeded(from, to, amount); } (success, returnData) = to.call.gas(gasLimit)(data); assert(gasleft() > gasLimit / 63); // not enough gas provided, assert to throw all gas // TODO use EIP-1930 } function _transfer(address from, address to, uint256 amount) internal; function _addAllowanceIfNeeded(address owner, address spender, uint256 amountNeeded) internal; }