Transaction Hash:
Block:
22814517 at Jun-30-2025 03:22:23 AM +UTC
Transaction Fee:
0.000011860548866355 ETH
$0.05
Gas Used:
29,505 Gas / 0.401984371 Gwei
Emitted Events:
312 |
SDAO.Transfer( from=[Sender] 0xc7d4029aca6f9e2a8bcda06323dd14cc8c6e083e, to=0x4f8E0e2c1adb6bebb0Bb36a24716C578dfC86Cd2, value=35000000000000000000 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x95222290...5CC4BAfe5
Miner
| (beaverbuild) | 41.875489643942589168 Eth | 41.875489645714482438 Eth | 0.00000000177189327 | |
0xc20059e0...50781b066 | |||||
0xC7d4029A...C8c6E083E |
0.000030000787280208 Eth
Nonce: 1
|
0.000018140238413853 Eth
Nonce: 2
| 0.000011860548866355 |
Execution Trace
SDAO.transfer( to=0x4f8E0e2c1adb6bebb0Bb36a24716C578dfC86Cd2, value=35000000000000000000 ) => ( True )
transfer[SDAO (ln:174)]
Transfer[SDAO (ln:184)]
// SPDX-FileCopyrightText: © 2017, 2018, 2019 dbrock, rain, mrchico // SPDX-FileCopyrightText: © 2023 Dai Foundation <www.daifoundation.org> // SPDX-License-Identifier: AGPL-3.0-or-later // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. pragma solidity ^0.8.16; /// @dev Smart Contract signature validation interface. interface IERC1271 { function isValidSignature(bytes32, bytes memory) external view returns (bytes4); } /** * @title SDAO: SubDAO-level governance token. * @dev This is a port from X-Domain Dai implementation: https://www.diffchecker.com/XeqEiDcn/ with additional features: * - Actors with owner access (`wards`) can update `name` and `symbol`. * @author @amusingaxl */ contract SDAO { /// @notice Addresses with owner access on this contract. `wards[usr]` mapping(address => uint256) public wards; // --- ERC20 Data --- /// @dev The name of the token. string public name; /// @dev The symbol of the token. string public symbol; /// @dev The version of the token. string public constant version = "1"; /// @dev The number of decimal places for the token. uint8 public constant decimals = 18; /// @notice Returns the amount of tokens in existence. uint256 public totalSupply; /// @notice Returns the amount of tokens owned by `account`. balanceOf[account] mapping(address => uint256) public balanceOf; /// @notice The remaining number of tokens that `spender` will be allowed to spend on behalf of `owner` through {transferFrom}. allowance[owner][spender] mapping(address => mapping(address => uint256)) public allowance; /** * @notice Provides replay attack protection for ERC20 Permits. nonces[owner] * @dev This value must be included whenever a signature is generated for {permit}. * @dev Every successful call to {permit} increases `owner`'s nonce by one. */ mapping(address => uint256) public nonces; /** * @dev `usr` was granted owner access. * @param usr The user address. */ event Rely(address indexed usr); /** * @notice `usr` owner access was revoked. * @param usr The user address. */ event Deny(address indexed usr); /** * @notice A contract parameter was updated. * @param what The parameter being changed. One of: "name", "symbol". * @param data The new value of the parameter. */ event File(bytes32 indexed what, string data); /** * @notice Emitted when the allowance of a `spender` for an `owner` is set by a call to {approve}. * @param owner The account setting the allowance. * @param spender The account receiving the allowance. * @param value The new allowance. */ event Approval(address indexed owner, address indexed spender, uint256 value); /** * @notice Emitted when `value` tokens are moved from one account (`from`) to another (`to`). * @param from The source of the funds. * @param to The destination of the funds. * @param value The amount transfered. Note that `value` may be zero. */ event Transfer(address indexed from, address indexed to, uint256 value); // --- EIP712 niceties --- /// @dev The chain ID of the chain in which the token has been deployed. uint256 public immutable deploymentChainId; /// @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. bytes32 private immutable _DOMAIN_SEPARATOR; /// @dev ERC-712 typehash for permits. bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); modifier auth() { require(wards[msg.sender] == 1, "SDAO/not-authorized"); _; } /** * @param _name The name of the token. * @param _symbol The symbol of the token. */ constructor(string memory _name, string memory _symbol) { name = _name; symbol = _symbol; wards[msg.sender] = 1; emit Rely(msg.sender); deploymentChainId = block.chainid; _DOMAIN_SEPARATOR = _calculateDomainSeparator(block.chainid); } /** * @dev Calculates the EIP-712 domain separator for permits. * @param chainId The required chain ID. * @return The keccak256 hash of the EIP-712 identifier. */ function _calculateDomainSeparator(uint256 chainId) private view returns (bytes32) { return keccak256( abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), keccak256(bytes(name)), keccak256(bytes(version)), chainId, address(this) ) ); } /** * @notice Calculates the EIP-712 domain separator for permits. * @dev To prevent replay attacks after potential chain splits, the cached domain separator is used only if the * current chain ID matches the cached chain ID. Otherwise, the domain separator is recalculated every time. * @return The keccak256 hash of the EIP-712 identifier. */ function DOMAIN_SEPARATOR() external view returns (bytes32) { return block.chainid == deploymentChainId ? _DOMAIN_SEPARATOR : _calculateDomainSeparator(block.chainid); } // --- Administration --- /** * @notice Grants `usr` admin access to this contract. * @param usr The user address. */ function rely(address usr) external auth { wards[usr] = 1; emit Rely(usr); } /** * @notice Revokes `usr` admin access from this contract. * @param usr The user address. */ function deny(address usr) external auth { wards[usr] = 0; emit Deny(usr); } /** * @notice Updates token parameters. * @dev There are no mechanisms to prevent governance from changing token parameters more than once. * We assume that the enforcement will be handled off-chain through governance artifacts. * @param what The parameter being changed. One of: "name", "symbol". * @param data The updated value for the parameter. */ function file(bytes32 what, string calldata data) external auth { if (what == "name") { name = data; } else if (what == "symbol") { symbol = data; } else { revert("SDAO/file-unrecognized-param"); } emit File(what, data); } // --- ERC20 Mutations --- /** * @notice Moves `amount` tokens from `msg.sender` to `to`. * @dev Emits a {Transfer} event. * @param to The destination for the tokens. * @param value The amount of tokens to transfer. * @return Always `true` if the transaction did not revert. */ function transfer(address to, uint256 value) external returns (bool) { require(to != address(0) && to != address(this), "SDAO/invalid-address"); uint256 balance = balanceOf[msg.sender]; require(balance >= value, "SDAO/insufficient-balance"); unchecked { balanceOf[msg.sender] = balance - value; // Note: safe as the sum of all balances is equal to `totalSupply`; // any overflow would have occurred already when increasing `totalSupply` balanceOf[to] += value; } emit Transfer(msg.sender, to, value); return true; } /** * @notice Moves `amount` tokens from `from` to `to` using the allowance mechanism. * @dev Emits a {Transfer} event. * @param from The origin of the tokens. * @param to The destination for the tokens. * @param value The amount of tokens to transfer. * @return Always `true` if the transaction did not revert. */ function transferFrom(address from, address to, uint256 value) external returns (bool) { require(to != address(0) && to != address(this), "SDAO/invalid-address"); uint256 balance = balanceOf[from]; require(balance >= value, "SDAO/insufficient-balance"); if (from != msg.sender) { uint256 allowed = allowance[from][msg.sender]; if (allowed != type(uint256).max) { require(allowed >= value, "SDAO/insufficient-allowance"); unchecked { allowance[from][msg.sender] = allowed - value; } } } unchecked { balanceOf[from] = balance - value; // Note: safe as the sum of all balances is equal to `totalSupply`; // any overflow would have occurred already when increasing `totalSupply` balanceOf[to] += value; } emit Transfer(from, to, value); return true; } /** * @notice Sets `amount` as the allowance of `spender` over `msg.sender` tokens. * @dev Emits an {Approval} event. * @param spender The account receiving the allowance. * @param value The amount for allowance. * @return Always `true` if the transaction did not revert. * * @dev 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 */ function approve(address spender, uint256 value) external returns (bool) { allowance[msg.sender][spender] = value; emit Approval(msg.sender, spender, value); return true; } // --- Mint/Burn --- /** * @notice Creates `amount` tokens and assigns them to `to`, increasing the total supply. * @dev Only authorized parties can call this function. * @dev `to` must not be the zero address. * @dev Emits a {Transfer} event with `from` set to the zero address. * @param to The destination for the minted tokens. * @param value The amount of tokens to mint. */ function mint(address to, uint256 value) external auth { require(to != address(0) && to != address(this), "SDAO/invalid-address"); unchecked { // Note: safe as the sum of all balances is equal to `totalSupply`; // there is already an overvlow check below balanceOf[to] = balanceOf[to] + value; } totalSupply = totalSupply + value; emit Transfer(address(0), to, value); } /** * @notice Destroys `amount` tokens and assigns them to `to`, decreasing the total supply. * @dev If `from` != `msg.sender`, it uses the allowance mechanism. * @dev Emits a {Transfer} event with `to` set to the zero address. * @param from The origin for the burnt tokens. * @param value The amount of tokens to burn. */ function burn(address from, uint256 value) external { uint256 balance = balanceOf[from]; require(balance >= value, "SDAO/insufficient-balance"); if (from != msg.sender) { uint256 allowed = allowance[from][msg.sender]; if (allowed != type(uint256).max) { require(allowed >= value, "SDAO/insufficient-allowance"); unchecked { allowance[from][msg.sender] = allowed - value; } } } unchecked { // Note: we don't need an underflow check here b/c `balance >= value` balanceOf[from] = balance - value; // Note: we don't need an underflow check here b/c `totalSupply >= balance >= value` totalSupply = totalSupply - value; } emit Transfer(from, address(0), value); } // --- Approve by signature --- /** * @notice Validates a `signature` of `digest` from `signer`. * @dev This function supports both EOA signature validation through ecrecover and EIP-1271 style smart contract * signature validation. * @param signer The signer account or smart contract. * @param digest The hash of the message being signed. * @param signature The signature. * @return Whether the signature is valid or not. */ function _isValidSignature(address signer, bytes32 digest, bytes memory signature) internal view returns (bool) { if (signature.length == 65) { bytes32 r; bytes32 s; uint8 v; assembly { r := mload(add(signature, 0x20)) s := mload(add(signature, 0x40)) v := byte(0, mload(add(signature, 0x60))) } if (signer == ecrecover(digest, v, r, s)) { return true; } } if (signer.code.length > 0) { (bool success, bytes memory result) = signer.staticcall( abi.encodeCall(IERC1271.isValidSignature, (digest, signature)) ); return (success && result.length == 32 && abi.decode(result, (bytes4)) == IERC1271.isValidSignature.selector); } return false; } /** * @notice Sets `value` as the allowance of `spender` over `owner`'s tokens, given `owner`'s signed approval. * @dev Emits an {Approval} event. * @param owner The account setting the allowance through permit. * @param spender The account receiving the allowance through permit. CANNOT be the zero address. * @param value The amount for allowance through permit. * @param deadline Until when the permit is valid. MUST be a timestamp in the future. * @param signature The signature for the permit. MUST use `owner`'s current nonce (see {nonces}). * * @dev IMPORTANT: The same issues {IERC20-approve} has related to transaction ordering also apply here. */ function permit(address owner, address spender, uint256 value, uint256 deadline, bytes memory signature) public { require(block.timestamp <= deadline, "SDAO/permit-expired"); require(owner != address(0), "SDAO/invalid-owner"); uint256 nonce; unchecked { nonce = nonces[owner]++; } bytes32 digest = keccak256( abi.encodePacked( "\\x19\\x01", block.chainid == deploymentChainId ? _DOMAIN_SEPARATOR : _calculateDomainSeparator(block.chainid), keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonce, deadline)) ) ); require(_isValidSignature(owner, digest, signature), "SDAO/invalid-permit"); allowance[owner][spender] = value; emit Approval(owner, spender, value); } /** * @notice Sets `value` as the allowance of `spender` over `owner`'s tokens, given `owner`'s signed approval. * @dev Emits an {Approval} event. * @param owner The account setting the allowance through permit. * @param spender The account receiving the allowance through permit. CANNOT be the zero address. * @param value The amount for allowance through permit. * @param deadline Until when the permit is valid. MUST be a timestamp in the future. * @param v Ethereum signature recovery ID. * @param r Ethereum ECDSA signature output. * @param s Ethereum ECDSA signature output. * * @dev IMPORTANT: The same issues {IERC20-approve} has related to transaction ordering also apply here. */ function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) external { permit(owner, spender, value, deadline, abi.encodePacked(r, s, v)); } }