Transaction Hash:
Block:
18588109 at Nov-17-2023 12:45:47 AM +UTC
Transaction Fee:
0.00669751527591432 ETH
$16.96
Gas Used:
185,328 Gas / 36.138712315 Gwei
Emitted Events:
87 |
NerveMultiSigWalletIII.TransferFunds( to=0xf8aebeb76a87b1fac30524b4024b4afb8cab4b01, amount=11500000000000000 )
|
88 |
NerveMultiSigWalletIII.TxWithdrawCompleted( txKey=dc585df0b1c146508e0009cdb7b3fffdeaad95c62beec89a01d050d4c3b05682 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x95222290...5CC4BAfe5
Miner
| (beaverbuild) | 7.627368268428856995 Eth | 7.629703541428006371 Eth | 0.002335272999149376 | |
0xC707E085...d937d7E82 | (NerveNetwork: Bank Node Multi-Signature) | 47.131465993720053045 Eth | 47.119965993720053045 Eth | 0.0115 | |
0xd87F2ad3...CA71B3B56 |
0.169229441461638173 Eth
Nonce: 1420
|
0.162531926185723853 Eth
Nonce: 1421
| 0.00669751527591432 | ||
0xf8aeBeb7...b8cab4B01 | 0.017490943048462699 Eth | 0.028990943048462699 Eth | 0.0115 |
Execution Trace
NerveMultiSigWalletIII.createOrSignWithdraw( txKey=dc585df0b1c146508e0009cdb7b3fffdeaad95c62beec89a01d050d4c3b05682, to=0xf8aeBeb76A87b1faC30524b4024B4aFb8cab4B01, amount=11500000000000000, isERC20=False, ERC20=0x0000000000000000000000000000000000000000, signatures=0x051D5F3C403646496864B7B016F59F9251A144A9FF359E3C2D6CA6DC93589166270F22A32EF15B568D72327D7AB5AE3073FA7F37E69F818766DB250E1FF166A11C8C5A240AAA7AF7450A8A1A74C01182284DCFBAB1FF6EB0F02E25B5ABB81BCA3C137C0C2C8C90DDDA8DAB6D66DABFF1A44E721CAE6A70713DF4A7B6C0613E89A81C9AB2317CBC7FEA7031CBC3590F09C92CF33F14C2F8907BA55FBBA3906370252074024BF712C2F0AC5BEE477A70EB6DE71D9FA8574F4C7FECD64FBECC0587940C1BA6644DAEB87E0F2B7F167BF4049FBF7046176CBA4516E7C8D196D57684AB752049B107BEF99E47F2389109B092D38AD9B6449CA2A192B2891FB94E1FF0669FFB1C704505DAA9696CEDD9015AE3EACB084E0D20348678C4DE1C303D263AAEE7359D6FC2E80F497E7A2D49AC735C46F88EAB1DA517CCFCBDC0F93D9334D92BC65ED11BA643AB6FC2F88D1118AA1B5D03F755E514DD2DA513C65D87E43C79E2166774275ED9CA22C9E29E45CC8D08DCD8B59FFDD9C6764F551F0BF6EA1F7C38947DB9C71C534F526D3BF70F6AA417D15DF32FBA4BE82EB4DDE45512C662E2D6822CF9B1983A7FA677563B65CDC7602E780AC99BD83BD58A4586EB3E4665646797388CCFF01C5B54EF9D71FD0DA386461C5F29A0628DCC4AE66F348E6F91FFBA5932A62EAD1E75BB2C6B71FFD66929D53B5657F51D337022A61B45CC6090478365AD5F288EED1B483ABBC4A8DAEDD33F5BE8F9FA4FF36D490DF102E9603B37041DDC092C1E42C904C6E1F0804DCDAFCF9CA45F49C6AB98CAC461CB96C068308D77E8897520B2AB1B6E92511AAB45F8ECC9A19192F34853C3E8E7E16BC2CFFD88EB918092C1D705CC1835B022D2DE543E4AA6DF46471BCFF55E620C2961EFA2E4B3FCC0AC873653AB1C )
-
Null: 0x000...001.18c903a0( )
-
Null: 0x000...001.18c903a0( )
-
Null: 0x000...001.18c903a0( )
-
Null: 0x000...001.18c903a0( )
-
Null: 0x000...001.18c903a0( )
-
Null: 0x000...001.18c903a0( )
-
Null: 0x000...001.18c903a0( )
-
Null: 0x000...001.18c903a0( )
-
Null: 0x000...001.18c903a0( )
-
Null: 0x000...001.18c903a0( )
- ETH 0.0115
0xf8aebeb76a87b1fac30524b4024b4afb8cab4b01.CALL( )
createOrSignWithdraw[NerveMultiSigWalletIII (ln:982)]
validateTransferERC20[NerveMultiSigWalletIII (ln:990)]
isContract[NerveMultiSigWalletIII (ln:1222)]
isMinterERC20[NerveMultiSigWalletIII (ln:1223)]
balanceOf[NerveMultiSigWalletIII (ln:1228)]
validSignature[NerveMultiSigWalletIII (ln:998)]
getManagerFromSignatures[NerveMultiSigWalletIII (ln:1060)]
transferERC20[NerveMultiSigWalletIII (ln:1001)]
isMinterERC20[NerveMultiSigWalletIII (ln:1232)]
mint[NerveMultiSigWalletIII (ln:1235)]
balanceOf[NerveMultiSigWalletIII (ln:1239)]
safeTransfer[NerveMultiSigWalletIII (ln:1241)]
transfer[NerveMultiSigWalletIII (ln:1005)]
TransferFunds[NerveMultiSigWalletIII (ln:1006)]
completeTx[NerveMultiSigWalletIII (ln:1009)]
TxWithdrawCompleted[NerveMultiSigWalletIII (ln:1010)]
pragma solidity ^0.5.5; /** * @dev Interface of the ERC20 standard as defined in the EIP. Does not include * the optional functions; to access them see {ERC20Detailed}. */ 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); } /** * @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 ERC20;` statement to your contract, * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. */ library SafeERC20 { using SafeMath for uint256; using Address for address; function safeTransfer(IERC20 token, address to, uint256 value, mapping(address => uint8) storage bugERC20s) internal { callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value), bugERC20s); } function safeTransferFrom(IERC20 token, address from, address to, uint256 value, mapping(address => uint8) storage bugERC20s) internal { callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value), bugERC20s); } /** * @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, mapping(address => uint8) storage bugERC20s) 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. // A Solidity high level call has three parts: // 1. The target address is checked to verify it contains contract code // 2. The call itself is made, and success asserted // 3. The return value is decoded, which in turn checks the size of the returned data. // solhint-disable-next-line max-line-length require(address(token).isContract(), "SafeERC20: call to non-contract"); // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = address(token).call(data); require(success, "SafeERC20: low-level call failed"); if (bugERC20s[address(token)] != 0) { return; } 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"); } } } // Part: ReentrancyGuard /** * @dev Contract module that helps prevent reentrant calls to a function. * * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier * available, which can be applied to functions to make sure there are no nested * (reentrant) calls to them. * * Note that because there is a single `nonReentrant` guard, functions marked as * `nonReentrant` may not call one another. This can be worked around by making * those functions `private`, and then adding `external` `nonReentrant` entry * points to them. * * TIP: If you would like to learn more about reentrancy and alternative ways * to protect against it, check out our blog post * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. */ contract ReentrancyGuard { // Booleans are more expensive than uint256 or any type that takes up a full // word because each write operation emits an extra SLOAD to first read the // slot's contents, replace the bits taken up by the boolean, and then write // back. This is the compiler's defense against contract upgrades and // pointer aliasing, and it cannot be disabled. // The values being non-zero value makes deployment a bit more expensive, // but in exchange the refund on every call to nonReentrant will be lower in // amount. Since refunds are capped to a percentage of the total // transaction's gas, it is best to keep them low in cases like this one, to // increase the likelihood of the full refund coming into effect. uint256 private constant _NOT_ENTERED = 1; uint256 private constant _ENTERED = 2; uint256 private _status; constructor () internal { _status = _NOT_ENTERED; } /** * @dev Prevents a contract from calling itself, directly or indirectly. * Calling a `nonReentrant` function from another `nonReentrant` * function is not supported. It is possible to prevent this from happening * by making the `nonReentrant` function external, and make it call a * `private` function that does the actual work. */ modifier nonReentrant() { // On the first call to nonReentrant, _notEntered will be true require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); // Any calls to nonReentrant after this point will fail _status = _ENTERED; _; // By storing the original value once again, a refund is triggered (see // https://eips.ethereum.org/EIPS/eip-2200) _status = _NOT_ENTERED; } } /** * @dev Wrappers over Solidity's arithmetic operations with added overflow * checks. * * Arithmetic operations in Solidity wrap on overflow. This can easily result * in bugs, because programmers usually assume that an overflow raises an * error, which is the standard behavior in high level programming languages. * `SafeMath` restores this intuition by reverting the transaction when 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. */ library SafeMath { /** * @dev Returns the addition of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `+` operator. * * Requirements: * - Addition cannot overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a, "SafeMath: addition overflow"); return c; } /** * @dev Returns the subtraction of two unsigned integers, reverting on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * - Subtraction cannot overflow. */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { return sub(a, b, "SafeMath: subtraction overflow"); } /** * @dev Returns the subtraction of two unsigned integers, reverting with custom message on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * - Subtraction cannot overflow. * * _Available since v2.4.0._ */ function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b <= a, errorMessage); uint256 c = a - b; return c; } /** * @dev Returns the multiplication of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `*` operator. * * Requirements: * - Multiplication cannot overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256) { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 if (a == 0) { return 0; } uint256 c = a * b; require(c / a == b, "SafeMath: multiplication overflow"); return c; } /** * @dev Returns the integer division of two unsigned integers. Reverts on * division by zero. The result is rounded towards zero. * * Counterpart to Solidity's `/` operator. Note: this function uses a * `revert` opcode (which leaves remaining gas untouched) while Solidity * uses an invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. */ function div(uint256 a, uint256 b) internal pure returns (uint256) { return div(a, b, "SafeMath: division by zero"); } /** * @dev Returns the integer division of two unsigned integers. Reverts with custom message on * division by zero. The result is rounded towards zero. * * Counterpart to Solidity's `/` operator. Note: this function uses a * `revert` opcode (which leaves remaining gas untouched) while Solidity * uses an invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. * * _Available since v2.4.0._ */ function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { // Solidity only automatically asserts when dividing by 0 require(b > 0, errorMessage); uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } /** * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * Reverts when dividing by zero. * * Counterpart to Solidity's `%` operator. This function uses a `revert` * opcode (which leaves remaining gas untouched) while Solidity uses an * invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. */ function mod(uint256 a, uint256 b) internal pure returns (uint256) { return mod(a, b, "SafeMath: modulo by zero"); } /** * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * Reverts with custom message when dividing by zero. * * Counterpart to Solidity's `%` operator. This function uses a `revert` * opcode (which leaves remaining gas untouched) while Solidity uses an * invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. * * _Available since v2.4.0._ */ function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b != 0, errorMessage); return a % b; } } /** * @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) { // 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; bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; // solhint-disable-next-line no-inline-assembly assembly { codehash := extcodehash(account) } return (codehash != accountHash && codehash != 0x0); } /** * @dev Converts an `address` into `address payable`. Note that this is * simply a type cast: the actual underlying value is not changed. * * _Available since v2.4.0._ */ function toPayable(address account) internal pure returns (address payable) { return address(uint160(account)); } /** * @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]. * * _Available since v2.4.0._ */ function sendValue(address payable recipient, uint256 amount) internal { require(address(this).balance >= amount, "Address: insufficient balance"); // solhint-disable-next-line avoid-call-value (bool success, ) = recipient.call.value(amount)(""); require(success, "Address: unable to send value, recipient may have reverted"); } } library BytesLib { function concat( bytes memory _preBytes, bytes memory _postBytes ) internal pure returns (bytes memory) { bytes memory tempBytes; assembly { // Get a location of some free memory and store it in tempBytes as // Solidity does for memory variables. tempBytes := mload(0x40) // Store the length of the first bytes array at the beginning of // the memory for tempBytes. let length := mload(_preBytes) mstore(tempBytes, length) // Maintain a memory counter for the current write location in the // temp bytes array by adding the 32 bytes for the array length to // the starting location. let mc := add(tempBytes, 0x20) // Stop copying when the memory counter reaches the length of the // first bytes array. let end := add(mc, length) for { // Initialize a copy counter to the start of the _preBytes data, // 32 bytes into its memory. let cc := add(_preBytes, 0x20) } lt(mc, end) { // Increase both counters by 32 bytes each iteration. mc := add(mc, 0x20) cc := add(cc, 0x20) } { // Write the _preBytes data into the tempBytes memory 32 bytes // at a time. mstore(mc, mload(cc)) } // Add the length of _postBytes to the current length of tempBytes // and store it as the new length in the first 32 bytes of the // tempBytes memory. length := mload(_postBytes) mstore(tempBytes, add(length, mload(tempBytes))) // Move the memory counter back from a multiple of 0x20 to the // actual end of the _preBytes data. mc := end // Stop copying when the memory counter reaches the new combined // length of the arrays. end := add(mc, length) for { let cc := add(_postBytes, 0x20) } lt(mc, end) { mc := add(mc, 0x20) cc := add(cc, 0x20) } { mstore(mc, mload(cc)) } // Update the free-memory pointer by padding our last write location // to 32 bytes: add 31 bytes to the end of tempBytes to move to the // next 32 byte block, then round down to the nearest multiple of // 32. If the sum of the length of the two arrays is zero then add // one before rounding down to leave a blank 32 bytes (the length block with 0). mstore(0x40, and( add(add(end, iszero(add(length, mload(_preBytes)))), 31), not(31) // Round down to the nearest 32 bytes. )) } return tempBytes; } function concatStorage(bytes storage _preBytes, bytes memory _postBytes) internal { assembly { // Read the first 32 bytes of _preBytes storage, which is the length // of the array. (We don't need to use the offset into the slot // because arrays use the entire slot.) let fslot := sload(_preBytes_slot) // Arrays of 31 bytes or less have an even value in their slot, // while longer arrays have an odd value. The actual length is // the slot divided by two for odd values, and the lowest order // byte divided by two for even values. // If the slot is even, bitwise and the slot with 255 and divide by // two to get the length. If the slot is odd, bitwise and the slot // with -1 and divide by two. let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2) let mlength := mload(_postBytes) let newlength := add(slength, mlength) // slength can contain both the length and contents of the array // if length < 32 bytes so let's prepare for that // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage switch add(lt(slength, 32), lt(newlength, 32)) case 2 { // Since the new array still fits in the slot, we just need to // update the contents of the slot. // uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length sstore( _preBytes_slot, // all the modifications to the slot are inside this // next block add( // we can just add to the slot contents because the // bytes we want to change are the LSBs fslot, add( mul( div( // load the bytes from memory mload(add(_postBytes, 0x20)), // zero all bytes to the right exp(0x100, sub(32, mlength)) ), // and now shift left the number of bytes to // leave space for the length in the slot exp(0x100, sub(32, newlength)) ), // increase length by the double of the memory // bytes length mul(mlength, 2) ) ) ) } case 1 { // The stored value fits in the slot, but the combined value // will exceed it. // get the keccak hash to get the contents of the array mstore(0x0, _preBytes_slot) let sc := add(keccak256(0x0, 0x20), div(slength, 32)) // save new length sstore(_preBytes_slot, add(mul(newlength, 2), 1)) // The contents of the _postBytes array start 32 bytes into // the structure. Our first read should obtain the `submod` // bytes that can fit into the unused space in the last word // of the stored array. To get this, we read 32 bytes starting // from `submod`, so the data we read overlaps with the array // contents by `submod` bytes. Masking the lowest-order // `submod` bytes allows us to add that value directly to the // stored value. let submod := sub(32, slength) let mc := add(_postBytes, submod) let end := add(_postBytes, mlength) let mask := sub(exp(0x100, submod), 1) sstore( sc, add( and( fslot, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00 ), and(mload(mc), mask) ) ) for { mc := add(mc, 0x20) sc := add(sc, 1) } lt(mc, end) { sc := add(sc, 1) mc := add(mc, 0x20) } { sstore(sc, mload(mc)) } mask := exp(0x100, sub(mc, end)) sstore(sc, mul(div(mload(mc), mask), mask)) } default { // get the keccak hash to get the contents of the array mstore(0x0, _preBytes_slot) // Start copying to the last used word of the stored array. let sc := add(keccak256(0x0, 0x20), div(slength, 32)) // save new length sstore(_preBytes_slot, add(mul(newlength, 2), 1)) // Copy over the first `submod` bytes of the new data as in // case 1 above. let slengthmod := mod(slength, 32) let mlengthmod := mod(mlength, 32) let submod := sub(32, slengthmod) let mc := add(_postBytes, submod) let end := add(_postBytes, mlength) let mask := sub(exp(0x100, submod), 1) sstore(sc, add(sload(sc), and(mload(mc), mask))) for { sc := add(sc, 1) mc := add(mc, 0x20) } lt(mc, end) { sc := add(sc, 1) mc := add(mc, 0x20) } { sstore(sc, mload(mc)) } mask := exp(0x100, sub(mc, end)) sstore(sc, mul(div(mload(mc), mask), mask)) } } } 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 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 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 equal(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) { bool success = true; assembly { let length := mload(_preBytes) // if lengths don't match the arrays are not equal switch eq(length, mload(_postBytes)) case 1 { // cb is a circuit breaker in the for loop since there's // no said feature for inline assembly loops // cb = 1 - don't breaker // cb = 0 - break let cb := 1 let mc := add(_preBytes, 0x20) let end := add(mc, length) for { let cc := add(_postBytes, 0x20) // the next line is the loop condition: // while(uint(mc < end) + cb == 2) } eq(add(lt(mc, end), cb), 2) { mc := add(mc, 0x20) cc := add(cc, 0x20) } { // if any of these checks fails then arrays are not equal if iszero(eq(mload(mc), mload(cc))) { // unsuccess: success := 0 cb := 0 } } } default { // unsuccess: success := 0 } } return success; } function equalStorage( bytes storage _preBytes, bytes memory _postBytes ) internal view returns (bool) { bool success = true; assembly { // we know _preBytes_offset is 0 let fslot := sload(_preBytes_slot) // Decode the length of the stored array like in concatStorage(). let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2) let mlength := mload(_postBytes) // if lengths don't match the arrays are not equal switch eq(slength, mlength) case 1 { // slength can contain both the length and contents of the array // if length < 32 bytes so let's prepare for that // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage if iszero(iszero(slength)) { switch lt(slength, 32) case 1 { // blank the last byte which is the length fslot := mul(div(fslot, 0x100), 0x100) if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) { // unsuccess: success := 0 } } default { // cb is a circuit breaker in the for loop since there's // no said feature for inline assembly loops // cb = 1 - don't breaker // cb = 0 - break let cb := 1 // get the keccak hash to get the contents of the array mstore(0x0, _preBytes_slot) let sc := keccak256(0x0, 0x20) let mc := add(_postBytes, 0x20) let end := add(mc, mlength) // the next line is the loop condition: // while(uint(mc < end) + cb == 2) for {} eq(add(lt(mc, end), cb), 2) { sc := add(sc, 1) mc := add(mc, 0x20) } { if iszero(eq(sload(sc), mload(mc))) { // unsuccess: success := 0 cb := 0 } } } } } default { // unsuccess: success := 0 } } return success; } } interface IERC20Minter { function mint(address to, uint256 amount) external; function burn(uint256 amount) external; function replaceMinter(address newMinter) external; } contract NerveMultiSigWalletIII is ReentrancyGuard { using Address for address; using SafeERC20 for IERC20; using SafeMath for uint256; using BytesLib for bytes; modifier isOwner{ require(owner == msg.sender, "Only owner can execute it"); _; } modifier isManager{ require(managers[msg.sender] == 1, "Only manager can execute it"); _; } bool public upgrade = false; address public upgradeContractAddress = address(0); // 最大管理员数量 uint public max_managers = 15; // 最小管理员数量 uint public min_managers = 3; // 最小签名比例 66% uint public rate = 66; // 签名字节长度 uint public signatureLength = 65; // 比例分母 uint constant DENOMINATOR = 100; // 当前合约版本 uint8 constant VERSION = 3; // hash计算加盐 uint public hashSalt; // 当前交易的最小签名数量 uint8 public current_min_signatures; address public owner; mapping(address => uint8) private seedManagers; address[] private seedManagerArray; mapping(address => uint8) private managers; address[] private managerArray; mapping(bytes32 => uint8) private completedKeccak256s; mapping(string => uint8) private completedTxs; mapping(address => uint8) private minterERC20s; mapping(address => uint8) public bugERC20s; bool public openCrossOutII = false; constructor(uint256 _chainid, address[] memory _managers) public{ require(_managers.length <= max_managers, "Exceeded the maximum number of managers"); require(_managers.length >= min_managers, "Not reaching the min number of managers"); owner = msg.sender; managerArray = _managers; for (uint8 i = 0; i < managerArray.length; i++) { managers[managerArray[i]] = 1; seedManagers[managerArray[i]] = 1; seedManagerArray.push(managerArray[i]); } require(managers[owner] == 0, "Contract creator cannot act as manager"); // 设置当前交易的最小签名数量 current_min_signatures = calMinSignatures(managerArray.length); hashSalt = _chainid * 2 + VERSION; } function() external payable { emit DepositFunds(msg.sender, msg.value); } function createOrSignWithdraw(string memory txKey, address payable to, uint256 amount, bool isERC20, address ERC20, bytes memory signatures) public nonReentrant isManager { require(bytes(txKey).length == 64, "Fixed length of txKey: 64"); require(to != address(0), "Withdraw: transfer to the zero address"); require(amount > 0, "Withdrawal amount must be greater than 0"); // 校验已经完成的交易 require(completedTxs[txKey] == 0, "Transaction has been completed"); // 校验提现金额 if (isERC20) { validateTransferERC20(ERC20, to, amount); } else { require(address(this).balance >= amount, "This contract address does not have sufficient balance of ether"); } bytes32 vHash = keccak256(abi.encodePacked(txKey, to, amount, isERC20, ERC20, hashSalt)); // 校验请求重复性 require(completedKeccak256s[vHash] == 0, "Invalid signatures"); // 校验签名 require(validSignature(vHash, signatures), "Valid signatures fail"); // 执行转账 if (isERC20) { transferERC20(ERC20, to, amount); } else { // 实际到账 require(address(this).balance >= amount, "This contract address does not have sufficient balance of ether"); to.transfer(amount); emit TransferFunds(to, amount); } // 保存交易数据 completeTx(txKey, vHash, 1); emit TxWithdrawCompleted(txKey); } function createOrSignManagerChange(string memory txKey, address[] memory adds, address[] memory removes, uint8 count, bytes memory signatures) public isManager { require(bytes(txKey).length == 64, "Fixed length of txKey: 64"); require(adds.length > 0 || removes.length > 0, "There are no managers joining or exiting"); // 校验已经完成的交易 require(completedTxs[txKey] == 0, "Transaction has been completed"); preValidateAddsAndRemoves(adds, removes); bytes32 vHash = keccak256(abi.encodePacked(txKey, adds, count, removes, hashSalt)); // 校验请求重复性 require(completedKeccak256s[vHash] == 0, "Invalid signatures"); // 校验签名 require(validSignature(vHash, signatures), "Valid signatures fail"); // 变更管理员 removeManager(removes); addManager(adds); // 更新当前交易的最小签名数 current_min_signatures = calMinSignatures(managerArray.length); // 保存交易数据 completeTx(txKey, vHash, 1); // add event emit TxManagerChangeCompleted(txKey); } function createOrSignUpgrade(string memory txKey, address upgradeContract, bytes memory signatures) public isManager { require(bytes(txKey).length == 64, "Fixed length of txKey: 64"); // 校验已经完成的交易 require(completedTxs[txKey] == 0, "Transaction has been completed"); require(!upgrade, "It has been upgraded"); require(upgradeContract.isContract(), "The address is not a contract address"); // 校验 bytes32 vHash = keccak256(abi.encodePacked(txKey, upgradeContract, hashSalt)); // 校验请求重复性 require(completedKeccak256s[vHash] == 0, "Invalid signatures"); // 校验签名 require(validSignature(vHash, signatures), "Valid signatures fail"); // 变更可升级 upgrade = true; upgradeContractAddress = upgradeContract; // 保存交易数据 completeTx(txKey, vHash, 1); // add event emit TxUpgradeCompleted(txKey); } function validSignature(bytes32 hash, bytes memory signatures) internal view returns (bool) { require(signatures.length <= 975, "Max length of signatures: 975"); // 获取签名列表对应的有效管理员,如果存在错误的签名、或者不是管理员的签名,就失败 uint sManagersCount = getManagerFromSignatures(hash, signatures); // 判断最小签名数量 return sManagersCount >= current_min_signatures; } function getManagerFromSignatures(bytes32 hash, bytes memory signatures) internal view returns (uint){ uint signCount = 0; uint times = signatures.length.div(signatureLength); address[] memory result = new address[](times); uint k = 0; uint8 j = 0; for (uint i = 0; i < times; i++) { bytes memory sign = signatures.slice(k, signatureLength); address mAddress = ecrecovery(hash, sign); require(mAddress != address(0), "Signatures error"); // 管理计数 if (managers[mAddress] == 1) { signCount++; result[j++] = mAddress; } k += signatureLength; } // 验证地址重复性 bool suc = repeatability(result); delete result; require(suc, "Signatures duplicate"); return signCount; } function validateRepeatability(address currentAddress, address[] memory list) internal pure returns (bool) { address tempAddress; for (uint i = 0; i < list.length; i++) { tempAddress = list[i]; if (tempAddress == address(0)) { break; } if (tempAddress == currentAddress) { return false; } } return true; } function repeatability(address[] memory list) internal pure returns (bool) { for (uint i = 0; i < list.length; i++) { address address1 = list[i]; if (address1 == address(0)) { break; } for (uint j = i + 1; j < list.length; j++) { address address2 = list[j]; if (address2 == address(0)) { break; } if (address1 == address2) { return false; } } } return true; } function ecrecovery(bytes32 hash, bytes memory sig) internal view returns (address) { bytes32 r; bytes32 s; uint8 v; if (sig.length != signatureLength) { return address(0); } assembly { r := mload(add(sig, 32)) s := mload(add(sig, 64)) v := byte(0, mload(add(sig, 96))) } if(uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { return address(0); } // https://github.com/ethereum/go-ethereum/issues/2053 if (v < 27) { v += 27; } if (v != 27 && v != 28) { return address(0); } return ecrecover(hash, v, r, s); } function preValidateAddsAndRemoves(address[] memory adds, address[] memory removes) internal view { // 校验adds uint addLen = adds.length; for (uint i = 0; i < addLen; i++) { address add = adds[i]; require(add != address(0), "ERROR: Detected zero address in adds"); require(managers[add] == 0, "The address list that is being added already exists as a manager"); } require(repeatability(adds), "Duplicate parameters for the address to join"); // 校验合约创建者不能被添加 require(validateRepeatability(owner, adds), "Contract creator cannot act as manager"); // 校验removes require(repeatability(removes), "Duplicate parameters for the address to exit"); uint removeLen = removes.length; for (uint i = 0; i < removeLen; i++) { address remove = removes[i]; require(seedManagers[remove] == 0, "Can't exit seed manager"); require(managers[remove] == 1, "There are addresses in the exiting address list that are not manager"); } require(managerArray.length + adds.length - removes.length <= max_managers, "Exceeded the maximum number of managers"); } /* 根据 `当前有效管理员数量` 和 `最小签名比例` 计算最小签名数量,向上取整 */ function calMinSignatures(uint managerCounts) internal view returns (uint8) { require(managerCounts > 0, "Manager Can't empty."); uint numerator = rate * managerCounts + DENOMINATOR - 1; return uint8(numerator / DENOMINATOR); } function removeManager(address[] memory removes) internal { if (removes.length == 0) { return; } for (uint i = 0; i < removes.length; i++) { delete managers[removes[i]]; } // 遍历修改前管理员列表 for (uint i = 0; i < managerArray.length; i++) { if (managers[managerArray[i]] == 0) { delete managerArray[i]; } } uint tempIndex = 0x10; for (uint i = 0; i<managerArray.length; i++) { address temp = managerArray[i]; if (temp == address(0)) { if (tempIndex == 0x10) tempIndex = i; continue; } else if (tempIndex != 0x10) { managerArray[tempIndex] = temp; tempIndex++; } } managerArray.length -= removes.length; } function addManager(address[] memory adds) internal { if (adds.length == 0) { return; } for (uint i = 0; i < adds.length; i++) { address add = adds[i]; if(managers[add] == 0) { managers[add] = 1; managerArray.push(add); } } } function completeTx(string memory txKey, bytes32 keccak256Hash, uint8 e) internal { completedTxs[txKey] = e; completedKeccak256s[keccak256Hash] = e; } function validateTransferERC20(address ERC20, address to, uint256 amount) internal view { require(to != address(0), "ERC20: transfer to the zero address"); require(address(this) != ERC20, "Do nothing by yourself"); require(ERC20.isContract(), "The address is not a contract address"); if (isMinterERC20(ERC20)) { // 定制ERC20验证结束 return; } IERC20 token = IERC20(ERC20); uint256 balance = token.balanceOf(address(this)); require(balance >= amount, "No enough balance of token"); } function transferERC20(address ERC20, address to, uint256 amount) internal { if (isMinterERC20(ERC20)) { // 定制的ERC20,跨链转入以太坊网络即增发 IERC20Minter minterToken = IERC20Minter(ERC20); minterToken.mint(to, amount); return; } IERC20 token = IERC20(ERC20); uint256 balance = token.balanceOf(address(this)); require(balance >= amount, "No enough balance of token"); token.safeTransfer(to, amount, bugERC20s); } function closeUpgrade() public isOwner { require(upgrade, "Denied"); upgrade = false; } function upgradeContractS1() public isOwner { require(upgrade, "Denied"); require(upgradeContractAddress != address(0), "ERROR: transfer to the zero address"); address(uint160(upgradeContractAddress)).transfer(address(this).balance); } function upgradeContractS2(address ERC20) public isOwner { require(upgrade, "Denied"); require(upgradeContractAddress != address(0), "ERROR: transfer to the zero address"); require(address(this) != ERC20, "Do nothing by yourself"); require(ERC20.isContract(), "The address is not a contract address"); IERC20 token = IERC20(ERC20); uint256 balance = token.balanceOf(address(this)); require(balance >= 0, "No enough balance of token"); token.safeTransfer(upgradeContractAddress, balance, bugERC20s); if (isMinterERC20(ERC20)) { // 定制的ERC20,转移增发销毁权限到新多签合约 IERC20Minter minterToken = IERC20Minter(ERC20); minterToken.replaceMinter(upgradeContractAddress); } } // 是否定制的ERC20 function isMinterERC20(address ERC20) public view returns (bool) { return minterERC20s[ERC20] > 0; } // 登记定制的ERC20 function registerMinterERC20(address ERC20) public isOwner { require(address(this) != ERC20, "Do nothing by yourself"); require(ERC20.isContract(), "The address is not a contract address"); require(!isMinterERC20(ERC20), "This address has already been registered"); minterERC20s[ERC20] = 1; } // 取消登记定制的ERC20 function unregisterMinterERC20(address ERC20) public isOwner { require(isMinterERC20(ERC20), "This address is not registered"); delete minterERC20s[ERC20]; } // 登记BUG的ERC20 function registerBugERC20(address bug) public isOwner { require(address(this) != bug, "Do nothing by yourself"); require(bug.isContract(), "The address is not a contract address"); bugERC20s[bug] = 1; } // 取消登记BUG的ERC20 function unregisterBugERC20(address bug) public isOwner { bugERC20s[bug] = 0; } // 从eth网络跨链转出资产(ETH or ERC20) function crossOut(string memory to, uint256 amount, address ERC20) public payable returns (bool) { address from = msg.sender; require(amount > 0, "ERROR: Zero amount"); if (ERC20 != address(0)) { require(msg.value == 0, "ERC20: Does not accept Ethereum Coin"); require(ERC20.isContract(), "The address is not a contract address"); IERC20 token = IERC20(ERC20); uint256 allowance = token.allowance(from, address(this)); require(allowance >= amount, "No enough amount for authorization"); uint256 fromBalance = token.balanceOf(from); require(fromBalance >= amount, "No enough balance of the token"); token.safeTransferFrom(from, address(this), amount, bugERC20s); if (isMinterERC20(ERC20)) { // 定制的ERC20,从以太坊网络跨链转出token即销毁 IERC20Minter minterToken = IERC20Minter(ERC20); minterToken.burn(amount); } } else { require(msg.value == amount, "Inconsistency Ethereum amount"); } emit CrossOutFunds(from, to, amount, ERC20); return true; } // 从eth网络跨链转出资产(ETH or ERC20) function crossOutII(string memory to, uint256 amount, address ERC20, bytes memory data) public payable returns (bool) { require(openCrossOutII, "CrossOutII: Not open"); address from = msg.sender; uint erc20Amount = 0; if (ERC20 != address(0)) { require(amount > 0, "ERROR: Zero amount"); require(ERC20.isContract(), "The address is not a contract address"); IERC20 token = IERC20(ERC20); uint256 allowance = token.allowance(from, address(this)); require(allowance >= amount, "No enough amount for authorization"); uint256 fromBalance = token.balanceOf(from); require(fromBalance >= amount, "No enough balance of the token"); token.safeTransferFrom(from, address(this), amount, bugERC20s); if (isMinterERC20(ERC20)) { // 定制的ERC20,从以太坊网络跨链转出token即销毁 IERC20Minter minterToken = IERC20Minter(ERC20); minterToken.burn(amount); } erc20Amount = amount; } else { require(msg.value > 0 && amount == 0, "CrossOutII: Illegal eth amount"); } emit CrossOutIIFunds(from, to, erc20Amount, ERC20, msg.value, data); return true; } function setCrossOutII(bool _open) public isOwner { openCrossOutII = _open; } function isCompletedTx(string memory txKey) public view returns (bool){ return completedTxs[txKey] > 0; } function ifManager(address _manager) public view returns (bool) { return managers[_manager] == 1; } function allManagers() public view returns (address[] memory) { return managerArray; } event DepositFunds(address from, uint amount); event CrossOutFunds(address from, string to, uint amount, address ERC20); event CrossOutIIFunds(address from, string to, uint amount, address ERC20, uint ethAmount, bytes data); event TransferFunds(address to, uint amount); event TxWithdrawCompleted(string txKey); event TxManagerChangeCompleted(string txKey); event TxUpgradeCompleted(string txKey); }