Transaction Hash:
Block:
20037466 at Jun-07-2024 04:09:11 AM +UTC
Transaction Fee:
0.00126340715046228 ETH
$3.30
Gas Used:
145,455 Gas / 8.685897016 Gwei
Emitted Events:
175 |
Airdrop.ClaimedVesting( id=A9536143447C7C1429BF0DDA32F509BDC3936F193DEE45155DD629ED93CE7575, account=[Receiver] GnosisSafeProxy, beneficiary=[Receiver] GnosisSafeProxy )
|
176 |
SafeToken.Transfer( from=Airdrop, to=[Receiver] GnosisSafeProxy, value=200204315195638650818 )
|
177 |
Airdrop.ClaimedVesting( id=8ADE7F1852E6F9CF73FFA20E421E376B019C647C84DA55074EC38C500C9505AE, account=[Receiver] GnosisSafeProxy, beneficiary=[Receiver] GnosisSafeProxy )
|
178 |
SafeToken.Transfer( from=Airdrop, to=[Receiver] GnosisSafeProxy, value=110670819046490755994 )
|
179 |
GnosisSafeProxy.0x442e715f626346e8c54381002da614f62bee8d27386535b2521ec8540898556e( 0x442e715f626346e8c54381002da614f62bee8d27386535b2521ec8540898556e, a01e754ab39ef41eabdedf4a17f948c6e405452b2662bdcb7156b7500c3805c1, 0000000000000000000000000000000000000000000000000000000000000000 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x3185FAD6...0ce2776E5 |
10.21266112855476074 Eth
Nonce: 755
|
10.21139772140429846 Eth
Nonce: 756
| 0.00126340715046228 | ||
0x5aFE3855...5e1c1eEEe | |||||
0x95222290...5CC4BAfe5
Miner
| (beaverbuild) | 14.230236404431192246 Eth | 14.230312094351504326 Eth | 0.00007568992031208 | |
0xA0b937D5...221544ee6 | (Safe: Safe User Airdrop) | ||||
0xC0fde70A...EE8cDb585 | (Safe: Safe User Airdrop SEP5) | ||||
0xD67C1EcE...8EfB79e43 |
Execution Trace
GnosisSafeProxy.6a761202( )
GnosisSafe.execTransaction( to=0x40A2aCCbd92BCA938b02010E17A5b8929b49130D, value=0, data=0x8D80FF0A0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000017200C0FDE70A65C7569FE919BE57492228DEE8CDB58500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064166BBD3BA9536143447C7C1429BF0DDA32F509BDC3936F193DEE45155DD629ED93CE7575000000000000000000000000D67C1ECEFCFFF4EB8A78F8ED87CD2C18EFB79E4300000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00A0B937D5C8E32A80E3A8ED4227CD020221544EE600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064166BBD3B8ADE7F1852E6F9CF73FFA20E421E376B019C647C84DA55074EC38C500C9505AE000000000000000000000000D67C1ECEFCFFF4EB8A78F8ED87CD2C18EFB79E4300000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000, operation=1, safeTxGas=0, baseGas=0, gasPrice=0, gasToken=0x0000000000000000000000000000000000000000, refundReceiver=0x0000000000000000000000000000000000000000, signatures=0x0000000000000000000000003185FAD637CF50BAE9AD59329ABE9060CE2776E50000000000000000000000000000000000000000000000000000000000000000012B0F017AE9BA4B721AB25D9AA7D80B82E0C504FEB00DC0545583878B7A25067D4917D8F45616D69F62C067FEB1934830CA20B43EB2162AECCC3E2DB1D5BAADD51C25B7C2EDC74D904568B43DEA7BB5C1E1E513149A977BFBDE4DE785CFE6B19C743BC49754E726945588845FA7FDB33C0ED64C7DBF424DDDA19C2BA3F5B3D4967D1C ) => ( success=True )
-
Null: 0x000...001.a01e754a( )
-
Null: 0x000...001.a01e754a( )
MultiSendCallOnly.multiSend( transactions=0x00C0FDE70A65C7569FE919BE57492228DEE8CDB58500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064166BBD3BA9536143447C7C1429BF0DDA32F509BDC3936F193DEE45155DD629ED93CE7575000000000000000000000000D67C1ECEFCFFF4EB8A78F8ED87CD2C18EFB79E4300000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00A0B937D5C8E32A80E3A8ED4227CD020221544EE600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064166BBD3B8ADE7F1852E6F9CF73FFA20E421E376B019C647C84DA55074EC38C500C9505AE000000000000000000000000D67C1ECEFCFFF4EB8A78F8ED87CD2C18EFB79E4300000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF )
Airdrop.claimVestedTokens( vestingId=A9536143447C7C1429BF0DDA32F509BDC3936F193DEE45155DD629ED93CE7575, beneficiary=0xD67C1EcEFcFff4EB8a78F8ED87Cd2c18EfB79e43, tokensToClaim=340282366920938463463374607431768211455 )
-
SafeToken.transfer( to=0xD67C1EcEFcFff4EB8a78F8ED87Cd2c18EfB79e43, amount=200204315195638650818 ) => ( True )
-
Airdrop.claimVestedTokens( vestingId=8ADE7F1852E6F9CF73FFA20E421E376B019C647C84DA55074EC38C500C9505AE, beneficiary=0xD67C1EcEFcFff4EB8a78F8ED87Cd2c18EfB79e43, tokensToClaim=340282366920938463463374607431768211455 )
-
SafeToken.transfer( to=0xD67C1EcEFcFff4EB8a78F8ED87Cd2c18EfB79e43, amount=110670819046490755994 ) => ( True )
-
-
File 1 of 6: GnosisSafeProxy
File 2 of 6: Airdrop
File 3 of 6: SafeToken
File 4 of 6: Airdrop
File 5 of 6: GnosisSafe
File 6 of 6: MultiSendCallOnly
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title IProxy - Helper interface to access masterCopy of the Proxy on-chain /// @author Richard Meissner - <[email protected]> interface IProxy { function masterCopy() external view returns (address); } /// @title GnosisSafeProxy - Generic proxy contract allows to execute all transactions applying the code of a master contract. /// @author Stefan George - <[email protected]> /// @author Richard Meissner - <[email protected]> contract GnosisSafeProxy { // singleton always needs to be first declared variable, to ensure that it is at the same location in the contracts to which calls are delegated. // To reduce deployment costs this variable is internal and needs to be retrieved via `getStorageAt` address internal singleton; /// @dev Constructor function sets address of singleton contract. /// @param _singleton Singleton address. constructor(address _singleton) { require(_singleton != address(0), "Invalid singleton address provided"); singleton = _singleton; } /// @dev Fallback function forwards all transactions and returns all received return data. fallback() external payable { // solhint-disable-next-line no-inline-assembly assembly { let _singleton := and(sload(0), 0xffffffffffffffffffffffffffffffffffffffff) // 0xa619486e == keccak("masterCopy()"). The value is right padded to 32-bytes with 0s if eq(calldataload(0), 0xa619486e00000000000000000000000000000000000000000000000000000000) { mstore(0, _singleton) return(0, 0x20) } calldatacopy(0, 0, calldatasize()) let success := delegatecall(gas(), _singleton, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) if eq(success, 0) { revert(0, returndatasize()) } return(0, returndatasize()) } } } /// @title Proxy Factory - Allows to create new proxy contact and execute a message call to the new proxy within one transaction. /// @author Stefan George - <[email protected]> contract GnosisSafeProxyFactory { event ProxyCreation(GnosisSafeProxy proxy, address singleton); /// @dev Allows to create new proxy contact and execute a message call to the new proxy within one transaction. /// @param singleton Address of singleton contract. /// @param data Payload for message call sent to new proxy contract. function createProxy(address singleton, bytes memory data) public returns (GnosisSafeProxy proxy) { proxy = new GnosisSafeProxy(singleton); if (data.length > 0) // solhint-disable-next-line no-inline-assembly assembly { if eq(call(gas(), proxy, 0, add(data, 0x20), mload(data), 0, 0), 0) { revert(0, 0) } } emit ProxyCreation(proxy, singleton); } /// @dev Allows to retrieve the runtime code of a deployed Proxy. This can be used to check that the expected Proxy was deployed. function proxyRuntimeCode() public pure returns (bytes memory) { return type(GnosisSafeProxy).runtimeCode; } /// @dev Allows to retrieve the creation code used for the Proxy deployment. With this it is easily possible to calculate predicted address. function proxyCreationCode() public pure returns (bytes memory) { return type(GnosisSafeProxy).creationCode; } /// @dev Allows to create new proxy contact using CREATE2 but it doesn't run the initializer. /// This method is only meant as an utility to be called from other methods /// @param _singleton Address of singleton contract. /// @param initializer Payload for message call sent to new proxy contract. /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract. function deployProxyWithNonce( address _singleton, bytes memory initializer, uint256 saltNonce ) internal returns (GnosisSafeProxy proxy) { // If the initializer changes the proxy address should change too. Hashing the initializer data is cheaper than just concatinating it bytes32 salt = keccak256(abi.encodePacked(keccak256(initializer), saltNonce)); bytes memory deploymentData = abi.encodePacked(type(GnosisSafeProxy).creationCode, uint256(uint160(_singleton))); // solhint-disable-next-line no-inline-assembly assembly { proxy := create2(0x0, add(0x20, deploymentData), mload(deploymentData), salt) } require(address(proxy) != address(0), "Create2 call failed"); } /// @dev Allows to create new proxy contact and execute a message call to the new proxy within one transaction. /// @param _singleton Address of singleton contract. /// @param initializer Payload for message call sent to new proxy contract. /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract. function createProxyWithNonce( address _singleton, bytes memory initializer, uint256 saltNonce ) public returns (GnosisSafeProxy proxy) { proxy = deployProxyWithNonce(_singleton, initializer, saltNonce); if (initializer.length > 0) // solhint-disable-next-line no-inline-assembly assembly { if eq(call(gas(), proxy, 0, add(initializer, 0x20), mload(initializer), 0, 0), 0) { revert(0, 0) } } emit ProxyCreation(proxy, _singleton); } /// @dev Allows to create new proxy contact, execute a message call to the new proxy and call a specified callback within one transaction /// @param _singleton Address of singleton contract. /// @param initializer Payload for message call sent to new proxy contract. /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract. /// @param callback Callback that will be invoced after the new proxy contract has been successfully deployed and initialized. function createProxyWithCallback( address _singleton, bytes memory initializer, uint256 saltNonce, IProxyCreationCallback callback ) public returns (GnosisSafeProxy proxy) { uint256 saltNonceWithCallback = uint256(keccak256(abi.encodePacked(saltNonce, callback))); proxy = createProxyWithNonce(_singleton, initializer, saltNonceWithCallback); if (address(callback) != address(0)) callback.proxyCreated(proxy, _singleton, initializer, saltNonce); } /// @dev Allows to get the address for a new proxy contact created via `createProxyWithNonce` /// This method is only meant for address calculation purpose when you use an initializer that would revert, /// therefore the response is returned with a revert. When calling this method set `from` to the address of the proxy factory. /// @param _singleton Address of singleton contract. /// @param initializer Payload for message call sent to new proxy contract. /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract. function calculateCreateProxyWithNonceAddress( address _singleton, bytes calldata initializer, uint256 saltNonce ) external returns (GnosisSafeProxy proxy) { proxy = deployProxyWithNonce(_singleton, initializer, saltNonce); revert(string(abi.encodePacked(proxy))); } } interface IProxyCreationCallback { function proxyCreated( GnosisSafeProxy proxy, address _singleton, bytes calldata initializer, uint256 saltNonce ) external; }
File 2 of 6: Airdrop
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.8.0 <0.9.0; import "./vendor/@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "./vendor/@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; import "./interfaces/ModuleManager.sol"; import "./VestingPool.sol"; /// @title Airdrop contract /// @author Richard Meissner - @rmeissner contract Airdrop is VestingPool { // Root of the Merkle tree bytes32 public root; // Time until which the airdrop can be redeemed uint64 public immutable redeemDeadline; /// @notice Creates the airdrop for the token at address `_token` and `_manager` as the manager. The airdrop can be redeemed until `_redeemDeadline`. /// @param _token The token that should be used for the airdrop /// @param _manager The manager of this airdrop (e.g. the address that can call `initializeRoot`) /// @param _redeemDeadline The deadline until when the airdrop could be redeemed (if inititalized). This needs to be a date in the future. constructor( address _token, address _manager, uint64 _redeemDeadline ) VestingPool(_token, _manager) { require(_redeemDeadline > block.timestamp, "Redeem deadline should be in the future"); redeemDeadline = _redeemDeadline; } /// @notice Initialize the airdrop with `_root` as the Merkle root. /// @dev This can only be called once /// @param _root The Merkle root that should be set for this contract function initializeRoot(bytes32 _root) public onlyPoolManager { require(root == bytes32(0), "State root already initialized"); root = _root; } /// @notice Creates a vesting authorized by the Merkle proof. /// @dev It is required that the pool has enough tokens available /// @dev Vesting will be created for msg.sender /// @param curveType Type of the curve that should be used for the vesting /// @param durationWeeks The duration of the vesting in weeks /// @param startDate The date when the vesting should be started (can be in the past) /// @param amount Amount of tokens that should be vested in atoms /// @param proof Proof to redeem tokens function redeem( uint8 curveType, uint16 durationWeeks, uint64 startDate, uint128 amount, bytes32[] calldata proof ) external { require(block.timestamp <= redeemDeadline, "Deadline to redeem vesting has been exceeded"); require(root != bytes32(0), "State root not initialized"); // This call will fail if the vesting was already created bytes32 vestingId = _addVesting(msg.sender, curveType, false, durationWeeks, startDate, amount); require(MerkleProof.verify(proof, root, vestingId), "Invalid merkle proof"); } /// @notice Claim `tokensToClaim` tokens from vesting `vestingId` and transfer them to the `beneficiary`. /// @dev This can only be called by the owner of the vesting /// @dev Beneficiary cannot be the 0-address /// @dev This will trigger a transfer of tokens via a module transaction /// @param vestingId Id of the vesting from which the tokens should be claimed /// @param beneficiary Account that should receive the claimed tokens /// @param tokensToClaim Amount of tokens to claim in atoms or max uint128 to claim all available function claimVestedTokensViaModule( bytes32 vestingId, address beneficiary, uint128 tokensToClaim ) public { uint128 tokensClaimed = updateClaimedTokens(vestingId, beneficiary, tokensToClaim); // Approve pool manager to transfer tokens on behalf of the pool require(IERC20(token).approve(poolManager, tokensClaimed), "Could not approve tokens"); // Check state prior to transfer uint256 balancePoolBefore = IERC20(token).balanceOf(address(this)); uint256 balanceBeneficiaryBefore = IERC20(token).balanceOf(beneficiary); // Build transfer data to call token contract via the pool manager bytes memory transferData = abi.encodeWithSignature( "transferFrom(address,address,uint256)", address(this), beneficiary, tokensClaimed ); // Trigger transfer of tokens from this pool to the beneficiary via the pool manager as a module transaction require(ModuleManager(poolManager).execTransactionFromModule(token, 0, transferData, 0), "Module transaction failed"); // Set allowance to 0 to avoid any left over allowance. (Note: this should be impossible for normal ERC20 tokens) require(IERC20(token).approve(poolManager, 0), "Could not set token allowance to 0"); // Check state after the transfer uint256 balancePoolAfter = IERC20(token).balanceOf(address(this)); uint256 balanceBeneficiaryAfter = IERC20(token).balanceOf(beneficiary); require(balancePoolAfter == balancePoolBefore - tokensClaimed, "Could not deduct tokens from pool"); require(balanceBeneficiaryAfter == balanceBeneficiaryBefore + tokensClaimed, "Could not add tokens to beneficiary"); } /// @notice Claims all tokens that have not been redeemed before `redeemDeadline` /// @dev Can only be called after `redeemDeadline` has been reached. /// @param beneficiary Account that should receive the claimed tokens function claimUnusedTokens(address beneficiary) external onlyPoolManager { require(block.timestamp > redeemDeadline, "Tokens can still be redeemed"); uint256 unusedTokens = tokensAvailableForVesting(); require(unusedTokens > 0, "No tokens to claim"); require(IERC20(token).transfer(beneficiary, unusedTokens), "Token transfer failed"); } /// @dev This method cannot be called on this contract function addVesting( address, uint8, bool, uint16, uint64, uint128 ) public pure override { revert("This method is not available for this contract"); } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.8.0 <0.9.0; import "./vendor/@openzeppelin/contracts/token/ERC20/IERC20.sol"; /// @title Vesting contract for multiple accounts /// @author Richard Meissner - @rmeissner contract VestingPool { event AddedVesting(bytes32 indexed id, address indexed account); event ClaimedVesting(bytes32 indexed id, address indexed account, address indexed beneficiary); event PausedVesting(bytes32 indexed id); event UnpausedVesting(bytes32 indexed id); event CancelledVesting(bytes32 indexed id); // Sane limits based on: https://eips.ethereum.org/EIPS/eip-1985 // amountClaimed should always be equal to or less than amount // pausingDate should always be equal to or greater than startDate struct Vesting { // First storage slot address account; // 20 bytes uint8 curveType; // 1 byte -> Max 256 different curve types bool managed; // 1 byte uint16 durationWeeks; // 2 bytes -> Max 65536 weeks ~ 1260 years uint64 startDate; // 8 bytes -> Works until year 292278994, but not before 1970 // Second storage slot uint128 amount; // 16 bytes -> Max 3.4e20 tokens (including decimals) uint128 amountClaimed; // 16 bytes -> Max 3.4e20 tokens (including decimals) // Third storage slot uint64 pausingDate; // 8 bytes -> Works until year 292278994, but not before 1970 bool cancelled; // 1 byte } // keccak256( // "EIP712Domain(uint256 chainId,address verifyingContract)" // ); bytes32 private constant DOMAIN_SEPARATOR_TYPEHASH = 0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218; // keccak256( // "Vesting(address account,uint8 curveType,bool managed,uint16 durationWeeks,uint64 startDate,uint128 amount)" // ); bytes32 private constant VESTING_TYPEHASH = 0x43838b5ce9ca440d1ac21b07179a1fdd88aa2175e5ea103f6e37aa6d18ce78ad; address public immutable token; address public immutable poolManager; uint256 public totalTokensInVesting; mapping(bytes32 => Vesting) public vestings; modifier onlyPoolManager() { require(msg.sender == poolManager, "Can only be called by pool manager"); _; } constructor(address _token, address _poolManager) { token = _token; poolManager = _poolManager; } /// @notice Create a vesting on this pool for `account`. /// @dev This can only be called by the pool manager /// @dev It is required that the pool has enough tokens available /// @param account The account for which the vesting is created /// @param curveType Type of the curve that should be used for the vesting /// @param managed Boolean that indicates if the vesting can be managed by the pool manager /// @param durationWeeks The duration of the vesting in weeks /// @param startDate The date when the vesting should be started (can be in the past) /// @param amount Amount of tokens that should be vested in atoms function addVesting( address account, uint8 curveType, bool managed, uint16 durationWeeks, uint64 startDate, uint128 amount ) public virtual onlyPoolManager { _addVesting(account, curveType, managed, durationWeeks, startDate, amount); } /// @notice Calculate the amount of tokens available for new vestings. /// @dev This value changes when more tokens are deposited to this contract /// @return Amount of tokens that can be used for new vestings. function tokensAvailableForVesting() public view virtual returns (uint256) { return IERC20(token).balanceOf(address(this)) - totalTokensInVesting; } /// @notice Create a vesting on this pool for `account`. /// @dev It is required that the pool has enough tokens available /// @dev Account cannot be zero address /// @param account The account for which the vesting is created /// @param curveType Type of the curve that should be used for the vesting /// @param managed Boolean that indicates if the vesting can be managed by the pool manager /// @param durationWeeks The duration of the vesting in weeks /// @param startDate The date when the vesting should be started (can be in the past) /// @param amount Amount of tokens that should be vested in atoms /// @param vestingId The id of the created vesting function _addVesting( address account, uint8 curveType, bool managed, uint16 durationWeeks, uint64 startDate, uint128 amount ) internal returns (bytes32 vestingId) { require(account != address(0), "Invalid account"); require(curveType < 2, "Invalid vesting curve"); vestingId = vestingHash(account, curveType, managed, durationWeeks, startDate, amount); require(vestings[vestingId].account == address(0), "Vesting id already used"); // Check that enough tokens are available for the new vesting uint256 availableTokens = tokensAvailableForVesting(); require(availableTokens >= amount, "Not enough tokens available"); // Mark tokens for this vesting in use totalTokensInVesting += amount; vestings[vestingId] = Vesting({ account: account, curveType: curveType, managed: managed, durationWeeks: durationWeeks, startDate: startDate, amount: amount, amountClaimed: 0, pausingDate: 0, cancelled: false }); emit AddedVesting(vestingId, account); } /// @notice Claim `tokensToClaim` tokens from vesting `vestingId` and transfer them to the `beneficiary`. /// @dev This can only be called by the owner of the vesting /// @dev Beneficiary cannot be the 0-address /// @dev This will trigger a transfer of tokens /// @param vestingId Id of the vesting from which the tokens should be claimed /// @param beneficiary Account that should receive the claimed tokens /// @param tokensToClaim Amount of tokens to claim in atoms or max uint128 to claim all available function claimVestedTokens( bytes32 vestingId, address beneficiary, uint128 tokensToClaim ) public { uint128 tokensClaimed = updateClaimedTokens(vestingId, beneficiary, tokensToClaim); require(IERC20(token).transfer(beneficiary, tokensClaimed), "Token transfer failed"); } /// @notice Update `amountClaimed` on vesting `vestingId` by `tokensToClaim` tokens. /// @dev This can only be called by the owner of the vesting /// @dev Beneficiary cannot be the 0-address /// @dev This will only update the internal state and NOT trigger the transfer of tokens. /// @param vestingId Id of the vesting from which the tokens should be claimed /// @param beneficiary Account that should receive the claimed tokens /// @param tokensToClaim Amount of tokens to claim in atoms or max uint128 to claim all available /// @param tokensClaimed Amount of tokens that have been newly claimed by calling this method function updateClaimedTokens( bytes32 vestingId, address beneficiary, uint128 tokensToClaim ) internal returns (uint128 tokensClaimed) { require(beneficiary != address(0), "Cannot claim to 0-address"); Vesting storage vesting = vestings[vestingId]; require(vesting.account == msg.sender, "Can only be claimed by vesting owner"); // Calculate how many tokens can be claimed uint128 availableClaim = _calculateVestedAmount(vesting) - vesting.amountClaimed; // If max uint128 is used, claim all available tokens. tokensClaimed = tokensToClaim == type(uint128).max ? availableClaim : tokensToClaim; require(tokensClaimed <= availableClaim, "Trying to claim too many tokens"); // Adjust how many tokens are locked in vesting totalTokensInVesting -= tokensClaimed; vesting.amountClaimed += tokensClaimed; emit ClaimedVesting(vestingId, vesting.account, beneficiary); } /// @notice Cancel vesting `vestingId`. /// @dev This can only be called by the pool manager /// @dev Only manageable vestings can be cancelled /// @param vestingId Id of the vesting that should be cancelled function cancelVesting(bytes32 vestingId) public onlyPoolManager { Vesting storage vesting = vestings[vestingId]; require(vesting.account != address(0), "Vesting not found"); require(vesting.managed, "Only managed vestings can be cancelled"); require(!vesting.cancelled, "Vesting already cancelled"); bool isFutureVesting = block.timestamp <= vesting.startDate; // If vesting is not already paused it will be paused // Pausing date should not be reset else tokens of the initial pause can be claimed if (vesting.pausingDate == 0) { // pausingDate should always be larger or equal to startDate vesting.pausingDate = isFutureVesting ? vesting.startDate : uint64(block.timestamp); } // Vesting is cancelled, therefore tokens that are not vested yet, will be added back to the pool uint128 unusedToken = isFutureVesting ? vesting.amount : vesting.amount - _calculateVestedAmount(vesting); totalTokensInVesting -= unusedToken; // Vesting is set to cancelled and therefore disallows unpausing vesting.cancelled = true; emit CancelledVesting(vestingId); } /// @notice Pause vesting `vestingId`. /// @dev This can only be called by the pool manager /// @dev Only manageable vestings can be paused /// @param vestingId Id of the vesting that should be paused function pauseVesting(bytes32 vestingId) public onlyPoolManager { Vesting storage vesting = vestings[vestingId]; require(vesting.account != address(0), "Vesting not found"); require(vesting.managed, "Only managed vestings can be paused"); require(vesting.pausingDate == 0, "Vesting already paused"); // pausingDate should always be larger or equal to startDate vesting.pausingDate = block.timestamp <= vesting.startDate ? vesting.startDate : uint64(block.timestamp); emit PausedVesting(vestingId); } /// @notice Unpause vesting `vestingId`. /// @dev This can only be called by the pool manager /// @dev Only vestings that have not been cancelled can be unpaused /// @param vestingId Id of the vesting that should be unpaused function unpauseVesting(bytes32 vestingId) public onlyPoolManager { Vesting storage vesting = vestings[vestingId]; require(vesting.account != address(0), "Vesting not found"); require(vesting.pausingDate != 0, "Vesting is not paused"); require(!vesting.cancelled, "Vesting has been cancelled and cannot be unpaused"); // Calculate the time the vesting was paused // If vesting has not started yet, then pausing date might be in the future uint64 timePaused = block.timestamp <= vesting.pausingDate ? 0 : uint64(block.timestamp) - vesting.pausingDate; // Offset the start date to create the effect of pausing vesting.startDate = vesting.startDate + timePaused; vesting.pausingDate = 0; emit UnpausedVesting(vestingId); } /// @notice Calculate vested and claimed token amounts for vesting `vestingId`. /// @dev This will revert if the vesting has not been started yet /// @param vestingId Id of the vesting for which to calculate the amounts /// @return vestedAmount The amount in atoms of tokens vested /// @return claimedAmount The amount in atoms of tokens claimed function calculateVestedAmount(bytes32 vestingId) external view returns (uint128 vestedAmount, uint128 claimedAmount) { Vesting storage vesting = vestings[vestingId]; require(vesting.account != address(0), "Vesting not found"); vestedAmount = _calculateVestedAmount(vesting); claimedAmount = vesting.amountClaimed; } /// @notice Calculate vested token amount for vesting `vesting`. /// @dev This will revert if the vesting has not been started yet /// @param vesting The vesting for which to calculate the amounts /// @return vestedAmount The amount in atoms of tokens vested function _calculateVestedAmount(Vesting storage vesting) internal view returns (uint128 vestedAmount) { require(vesting.startDate <= block.timestamp, "Vesting not active yet"); // Convert vesting duration to seconds uint64 durationSeconds = uint64(vesting.durationWeeks) * 7 * 24 * 60 * 60; // If contract is paused use the pausing date to calculate amount uint64 vestedSeconds = vesting.pausingDate > 0 ? vesting.pausingDate - vesting.startDate : uint64(block.timestamp) - vesting.startDate; if (vestedSeconds >= durationSeconds) { // If vesting time is longer than duration everything has been vested vestedAmount = vesting.amount; } else if (vesting.curveType == 0) { // Linear vesting vestedAmount = calculateLinear(vesting.amount, vestedSeconds, durationSeconds); } else if (vesting.curveType == 1) { // Exponential vesting vestedAmount = calculateExponential(vesting.amount, vestedSeconds, durationSeconds); } else { // This is unreachable because it is not possible to add a vesting with an invalid curve type revert("Invalid curve type"); } } /// @notice Calculate vested token amount on a linear curve. /// @dev Calculate vested amount on linear curve: targetAmount * elapsedTime / totalTime /// @param targetAmount Amount of tokens that is being vested /// @param elapsedTime Time that has elapsed for the vesting /// @param totalTime Duration of the vesting /// @return Tokens that have been vested on a linear curve function calculateLinear( uint128 targetAmount, uint64 elapsedTime, uint64 totalTime ) internal pure returns (uint128) { // Calculate vested amount on linear curve: targetAmount * elapsedTime / totalTime uint256 amount = (uint256(targetAmount) * uint256(elapsedTime)) / uint256(totalTime); require(amount <= type(uint128).max, "Overflow in curve calculation"); return uint128(amount); } /// @notice Calculate vested token amount on an exponential curve. /// @dev Calculate vested amount on exponential curve: targetAmount * elapsedTime^2 / totalTime^2 /// @param targetAmount Amount of tokens that is being vested /// @param elapsedTime Time that has elapsed for the vesting /// @param totalTime Duration of the vesting /// @return Tokens that have been vested on an exponential curve function calculateExponential( uint128 targetAmount, uint64 elapsedTime, uint64 totalTime ) internal pure returns (uint128) { // Calculate vested amount on exponential curve: targetAmount * elapsedTime^2 / totalTime^2 uint256 amount = (uint256(targetAmount) * uint256(elapsedTime) * uint256(elapsedTime)) / (uint256(totalTime) * uint256(totalTime)); require(amount <= type(uint128).max, "Overflow in curve calculation"); return uint128(amount); } /// @notice Calculate the id for a vesting based on its parameters. /// @dev The id is a EIP-712 based hash of the vesting. /// @param account The account for which the vesting was created /// @param curveType Type of the curve that is used for the vesting /// @param managed Indicator if the vesting is managed by the pool manager /// @param durationWeeks The duration of the vesting in weeks /// @param startDate The date when the vesting started (can be in the future) /// @param amount Amount of tokens that are vested in atoms /// @return vestingId Id of a vesting based on its parameters function vestingHash( address account, uint8 curveType, bool managed, uint16 durationWeeks, uint64 startDate, uint128 amount ) public view returns (bytes32 vestingId) { bytes32 domainSeparator = keccak256(abi.encode(DOMAIN_SEPARATOR_TYPEHASH, block.chainid, this)); bytes32 vestingDataHash = keccak256(abi.encode(VESTING_TYPEHASH, account, curveType, managed, durationWeeks, startDate, amount)); vestingId = keccak256(abi.encodePacked(bytes1(0x19), bytes1(0x01), domainSeparator, vestingDataHash)); } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.8.0 <0.9.0; interface ModuleManager { /// @notice Allows a module to execute a Safe transaction. /// @dev The implementation of the interface might require that the module is enabled (e.g. for the Safe contracts via enableModule) and could revert otherwise /// @param to Destination address of module transaction. /// @param value Ether value of module transaction. /// @param data Data payload of module transaction. /// @param operation Operation type of module transaction. /// @param success Indicates if the Safe transaction was executed successfully or not function execTransactionFromModule( address to, uint256 value, bytes memory data, uint8 operation ) external returns (bool success); } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/IERC20.sol) 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 `to`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address to, 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 `from` to `to` 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 from, address to, 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 // OpenZeppelin Contracts (last updated v4.5.0) (utils/cryptography/MerkleProof.sol) pragma solidity ^0.8.0; /** * @dev These functions deal with verification of Merkle Trees proofs. * * The proofs can be generated using the JavaScript library * https://github.com/miguelmota/merkletreejs[merkletreejs]. * Note: the hashing algorithm should be keccak256 and pair sorting should be enabled. * * See `test/utils/cryptography/MerkleProof.test.js` for some examples. */ library MerkleProof { /** * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree * defined by `root`. For this, a `proof` must be provided, containing * sibling hashes on the branch from the leaf to the root of the tree. Each * pair of leaves and each pair of pre-images are assumed to be sorted. */ function verify( bytes32[] memory proof, bytes32 root, bytes32 leaf ) internal pure returns (bool) { return processProof(proof, leaf) == root; } /** * @dev Returns the rebuilt hash obtained by traversing a Merklee tree up * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt * hash matches the root of the tree. When processing the proof, the pairs * of leafs & pre-images are assumed to be sorted. * * _Available since v4.4._ */ function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) { bytes32 computedHash = leaf; for (uint256 i = 0; i < proof.length; i++) { bytes32 proofElement = proof[i]; if (computedHash <= proofElement) { // Hash(current computed hash + current element of the proof) computedHash = _efficientHash(computedHash, proofElement); } else { // Hash(current element of the proof + current computed hash) computedHash = _efficientHash(proofElement, computedHash); } } return computedHash; } function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) { assembly { mstore(0x00, a) mstore(0x20, b) value := keccak256(0x00, 0x40) } } }
File 3 of 6: SafeToken
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.8.0 <0.9.0; import "./vendor/@openzeppelin/contracts/access/Ownable.sol"; import "./vendor/@openzeppelin/contracts/security/Pausable.sol"; import "./vendor/@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "./TokenRescuer.sol"; /// @title Safe Token contract /// @author Richard Meissner - @rmeissner contract SafeToken is ERC20, Pausable, Ownable, TokenRescuer { /// @dev Will mint 1 billion tokens to the owner and pause the contract constructor(address owner) ERC20("Safe Token", "SAFE") { // Transfer ownership immediately _transferOwnership(owner); // "ether" is used here to get 18 decimals _mint(owner, 1_000_000_000 ether); // Contract is paused by default // This has to be done after _mint, else minting will fail _pause(); } /// @notice Unpauses all token transfers. /// @dev See {Pausable-_unpause} /// Requirements: caller must be the owner function unpause() public virtual onlyOwner { require(paused(), "SafeToken: token is not paused"); _unpause(); } /// @dev See {ERC20-_beforeTokenTransfer} /// Requirements: the contract must not be paused OR transfer must be initiated by owner /// @param from The account that is sending the tokens /// @param to The account that should receive the tokens /// @param amount Amount of tokens that should be transferred function _beforeTokenTransfer( address from, address to, uint256 amount ) internal virtual override { super._beforeTokenTransfer(from, to, amount); require(to != address(this), "SafeToken: cannot transfer tokens to token contract"); // Token transfers are only possible if the contract is not paused // OR if triggered by the owner of the contract require(!paused() || owner() == _msgSender(), "SafeToken: token transfer while paused"); } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.8.0 <0.9.0; import "./vendor/@openzeppelin/contracts/access/Ownable.sol"; import "./vendor/@openzeppelin/contracts/token/ERC20/IERC20.sol"; /// @title Token Rescuer contract /// @author Richard Meissner - @rmeissner contract TokenRescuer is Ownable { /// @param token Token that should be rescued /// @param beneficiary The account that should receive the tokens /// @param amount Amount of tokens that should be rescued function _beforeTokenRescue( IERC20 token, address beneficiary, uint256 amount ) internal virtual {} /// @notice Transfer all tokens with address `token` owned by this contract to `beneficiary`. /// @dev This can only be called by the owner of the contract /// @param token The token that should be rescued /// @param beneficiary The account that should receive the tokens. function rescueToken(IERC20 token, address beneficiary) external onlyOwner { uint256 balanceToRescue = token.balanceOf(address(this)); require(balanceToRescue > 0, "TokenRescuer: No tokens to rescue"); _beforeTokenRescue(token, beneficiary, balanceToRescue); require(token.transfer(beneficiary, balanceToRescue), "TokenRescuer: Could not rescue token"); } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (access/Ownable.sol) pragma solidity ^0.8.0; import "../utils/Context.sol"; /** * @dev Contract module which provides a basic access control mechanism, where * there is an account (an owner) that can be granted exclusive access to * specific functions. * * By default, the owner account will be the one that deploys the contract. This * can later be changed with {transferOwnership}. * * This module is used through inheritance. It will make available the modifier * `onlyOwner`, which can be applied to your functions to restrict their use to * the owner. */ abstract contract Ownable is Context { address private _owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev Initializes the contract setting the deployer as the initial owner. */ constructor() { _transferOwnership(_msgSender()); } /** * @dev Returns the address of the current owner. */ function owner() public view virtual returns (address) { return _owner; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(owner() == _msgSender(), "Ownable: caller is not the owner"); _; } /** * @dev Leaves the contract without owner. It will not be possible to call * `onlyOwner` functions anymore. Can only be called by the current owner. * * NOTE: Renouncing ownership will leave the contract without an owner, * thereby removing any functionality that is only available to the owner. */ function renounceOwnership() public virtual onlyOwner { _transferOwnership(address(0)); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Can only be called by the current owner. */ function transferOwnership(address newOwner) public virtual onlyOwner { require(newOwner != address(0), "Ownable: new owner is the zero address"); _transferOwnership(newOwner); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Internal function without access restriction. */ function _transferOwnership(address newOwner) internal virtual { address oldOwner = _owner; _owner = newOwner; emit OwnershipTransferred(oldOwner, newOwner); } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (security/Pausable.sol) pragma solidity ^0.8.0; import "../utils/Context.sol"; /** * @dev Contract module which allows children to implement an emergency stop * mechanism that can be triggered by an authorized account. * * This module is used through inheritance. It will make available the * modifiers `whenNotPaused` and `whenPaused`, which can be applied to * the functions of your contract. Note that they will not be pausable by * simply including this module, only once the modifiers are put in place. */ abstract contract Pausable is Context { /** * @dev Emitted when the pause is triggered by `account`. */ event Paused(address account); /** * @dev Emitted when the pause is lifted by `account`. */ event Unpaused(address account); bool private _paused; /** * @dev Initializes the contract in unpaused state. */ constructor() { _paused = false; } /** * @dev Returns true if the contract is paused, and false otherwise. */ function paused() public view virtual returns (bool) { return _paused; } /** * @dev Modifier to make a function callable only when the contract is not paused. * * Requirements: * * - The contract must not be paused. */ modifier whenNotPaused() { require(!paused(), "Pausable: paused"); _; } /** * @dev Modifier to make a function callable only when the contract is paused. * * Requirements: * * - The contract must be paused. */ modifier whenPaused() { require(paused(), "Pausable: not paused"); _; } /** * @dev Triggers stopped state. * * Requirements: * * - The contract must not be paused. */ function _pause() internal virtual whenNotPaused { _paused = true; emit Paused(_msgSender()); } /** * @dev Returns to normal state. * * Requirements: * * - The contract must be paused. */ function _unpause() internal virtual whenPaused { _paused = false; emit Unpaused(_msgSender()); } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/ERC20.sol) pragma solidity ^0.8.0; import "./IERC20.sol"; import "./extensions/IERC20Metadata.sol"; import "../../utils/Context.sol"; /** * @dev Implementation of the {IERC20} interface. * * This implementation is agnostic to the way tokens are created. This means * that a supply mechanism has to be added in a derived contract using {_mint}. * For a generic mechanism see {ERC20PresetMinterPauser}. * * TIP: For a detailed writeup see our guide * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How * to implement supply mechanisms]. * * We have followed general OpenZeppelin Contracts guidelines: functions revert * instead returning `false` on failure. This behavior is nonetheless * conventional and does not conflict with the expectations of ERC20 * applications. * * Additionally, an {Approval} event is emitted on calls to {transferFrom}. * This allows applications to reconstruct the allowance for all accounts just * by listening to said events. Other implementations of the EIP may not emit * these events, as it isn't required by the specification. * * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} * functions have been added to mitigate the well-known issues around setting * allowances. See {IERC20-approve}. */ contract ERC20 is Context, IERC20, IERC20Metadata { mapping(address => uint256) private _balances; mapping(address => mapping(address => uint256)) private _allowances; uint256 private _totalSupply; string private _name; string private _symbol; /** * @dev Sets the values for {name} and {symbol}. * * The default value of {decimals} is 18. To select a different value for * {decimals} you should overload it. * * All two of these values are immutable: they can only be set once during * construction. */ constructor(string memory name_, string memory symbol_) { _name = name_; _symbol = symbol_; } /** * @dev Returns the name of the token. */ function name() public view virtual override returns (string memory) { return _name; } /** * @dev Returns the symbol of the token, usually a shorter version of the * name. */ function symbol() public view virtual override returns (string memory) { return _symbol; } /** * @dev Returns the number of decimals used to get its user representation. * For example, if `decimals` equals `2`, a balance of `505` tokens should * be displayed to a user as `5.05` (`505 / 10 ** 2`). * * Tokens usually opt for a value of 18, imitating the relationship between * Ether and Wei. This is the value {ERC20} uses, unless this function is * overridden; * * NOTE: This information is only used for _display_ purposes: it in * no way affects any of the arithmetic of the contract, including * {IERC20-balanceOf} and {IERC20-transfer}. */ function decimals() public view virtual override returns (uint8) { return 18; } /** * @dev See {IERC20-totalSupply}. */ function totalSupply() public view virtual override returns (uint256) { return _totalSupply; } /** * @dev See {IERC20-balanceOf}. */ function balanceOf(address account) public view virtual override returns (uint256) { return _balances[account]; } /** * @dev See {IERC20-transfer}. * * Requirements: * * - `to` cannot be the zero address. * - the caller must have a balance of at least `amount`. */ function transfer(address to, uint256 amount) public virtual override returns (bool) { address owner = _msgSender(); _transfer(owner, to, amount); return true; } /** * @dev See {IERC20-allowance}. */ function allowance(address owner, address spender) public view virtual override returns (uint256) { return _allowances[owner][spender]; } /** * @dev See {IERC20-approve}. * * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on * `transferFrom`. This is semantically equivalent to an infinite approval. * * Requirements: * * - `spender` cannot be the zero address. */ function approve(address spender, uint256 amount) public virtual override returns (bool) { address owner = _msgSender(); _approve(owner, spender, amount); return true; } /** * @dev See {IERC20-transferFrom}. * * Emits an {Approval} event indicating the updated allowance. This is not * required by the EIP. See the note at the beginning of {ERC20}. * * NOTE: Does not update the allowance if the current allowance * is the maximum `uint256`. * * Requirements: * * - `from` and `to` cannot be the zero address. * - `from` must have a balance of at least `amount`. * - the caller must have allowance for ``from``'s tokens of at least * `amount`. */ function transferFrom( address from, address to, uint256 amount ) public virtual override returns (bool) { address spender = _msgSender(); _spendAllowance(from, spender, amount); _transfer(from, to, amount); return true; } /** * @dev Atomically increases the allowance granted to `spender` by the caller. * * This is an alternative to {approve} that can be used as a mitigation for * problems described in {IERC20-approve}. * * Emits an {Approval} event indicating the updated allowance. * * Requirements: * * - `spender` cannot be the zero address. */ function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { address owner = _msgSender(); _approve(owner, spender, _allowances[owner][spender] + addedValue); return true; } /** * @dev Atomically decreases the allowance granted to `spender` by the caller. * * This is an alternative to {approve} that can be used as a mitigation for * problems described in {IERC20-approve}. * * Emits an {Approval} event indicating the updated allowance. * * Requirements: * * - `spender` cannot be the zero address. * - `spender` must have allowance for the caller of at least * `subtractedValue`. */ function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { address owner = _msgSender(); uint256 currentAllowance = _allowances[owner][spender]; require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); unchecked { _approve(owner, spender, currentAllowance - subtractedValue); } return true; } /** * @dev Moves `amount` of tokens from `sender` to `recipient`. * * This internal function is equivalent to {transfer}, and can be used to * e.g. implement automatic token fees, slashing mechanisms, etc. * * Emits a {Transfer} event. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `from` must have a balance of at least `amount`. */ function _transfer( address from, address to, uint256 amount ) internal virtual { require(from != address(0), "ERC20: transfer from the zero address"); require(to != address(0), "ERC20: transfer to the zero address"); _beforeTokenTransfer(from, to, amount); uint256 fromBalance = _balances[from]; require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); unchecked { _balances[from] = fromBalance - amount; } _balances[to] += amount; emit Transfer(from, to, amount); _afterTokenTransfer(from, to, amount); } /** @dev Creates `amount` tokens and assigns them to `account`, increasing * the total supply. * * Emits a {Transfer} event with `from` set to the zero address. * * Requirements: * * - `account` cannot be the zero address. */ function _mint(address account, uint256 amount) internal virtual { require(account != address(0), "ERC20: mint to the zero address"); _beforeTokenTransfer(address(0), account, amount); _totalSupply += amount; _balances[account] += amount; emit Transfer(address(0), account, amount); _afterTokenTransfer(address(0), account, amount); } /** * @dev Destroys `amount` tokens from `account`, reducing the * total supply. * * Emits a {Transfer} event with `to` set to the zero address. * * Requirements: * * - `account` cannot be the zero address. * - `account` must have at least `amount` tokens. */ function _burn(address account, uint256 amount) internal virtual { require(account != address(0), "ERC20: burn from the zero address"); _beforeTokenTransfer(account, address(0), amount); uint256 accountBalance = _balances[account]; require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); unchecked { _balances[account] = accountBalance - amount; } _totalSupply -= amount; emit Transfer(account, address(0), amount); _afterTokenTransfer(account, address(0), amount); } /** * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. * * This internal function is equivalent to `approve`, and can be used to * e.g. set automatic allowances for certain subsystems, etc. * * Emits an {Approval} event. * * Requirements: * * - `owner` cannot be the zero address. * - `spender` cannot be the zero address. */ function _approve( address owner, address spender, uint256 amount ) internal virtual { require(owner != address(0), "ERC20: approve from the zero address"); require(spender != address(0), "ERC20: approve to the zero address"); _allowances[owner][spender] = amount; emit Approval(owner, spender, amount); } /** * @dev Spend `amount` form the allowance of `owner` toward `spender`. * * Does not update the allowance amount in case of infinite allowance. * Revert if not enough allowance is available. * * Might emit an {Approval} event. */ function _spendAllowance( address owner, address spender, uint256 amount ) internal virtual { uint256 currentAllowance = allowance(owner, spender); if (currentAllowance != type(uint256).max) { require(currentAllowance >= amount, "ERC20: insufficient allowance"); unchecked { _approve(owner, spender, currentAllowance - amount); } } } /** * @dev Hook that is called before any transfer of tokens. This includes * minting and burning. * * Calling conditions: * * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens * will be transferred to `to`. * - when `from` is zero, `amount` tokens will be minted for `to`. * - when `to` is zero, `amount` of ``from``'s tokens will be burned. * - `from` and `to` are never both zero. * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ function _beforeTokenTransfer( address from, address to, uint256 amount ) internal virtual {} /** * @dev Hook that is called after any transfer of tokens. This includes * minting and burning. * * Calling conditions: * * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens * has been transferred to `to`. * - when `from` is zero, `amount` tokens have been minted for `to`. * - when `to` is zero, `amount` of ``from``'s tokens have been burned. * - `from` and `to` are never both zero. * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ function _afterTokenTransfer( address from, address to, uint256 amount ) internal virtual {} } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/IERC20.sol) 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 `to`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address to, 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 `from` to `to` 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 from, address to, 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 // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol) pragma solidity ^0.8.0; import "../IERC20.sol"; /** * @dev Interface for the optional metadata functions from the ERC20 standard. * * _Available since v4.1._ */ interface IERC20Metadata is IERC20 { /** * @dev Returns the name of the token. */ function name() external view returns (string memory); /** * @dev Returns the symbol of the token. */ function symbol() external view returns (string memory); /** * @dev Returns the decimals places of the token. */ function decimals() external view returns (uint8); } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/Context.sol) pragma solidity ^0.8.0; /** * @dev Provides information about the current execution context, including the * sender of the transaction and its data. While these are generally available * via msg.sender and msg.data, they should not be accessed in such a direct * manner, since when dealing with meta-transactions the account sending and * paying for execution may not be the actual sender (as far as an application * is concerned). * * This contract is only required for intermediate, library-like contracts. */ abstract contract Context { function _msgSender() internal view virtual returns (address) { return msg.sender; } function _msgData() internal view virtual returns (bytes calldata) { return msg.data; } }
File 4 of 6: Airdrop
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.8.0 <0.9.0; import "./vendor/@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "./vendor/@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; import "./interfaces/ModuleManager.sol"; import "./VestingPool.sol"; /// @title Airdrop contract /// @author Richard Meissner - @rmeissner contract Airdrop is VestingPool { // Root of the Merkle tree bytes32 public root; // Time until which the airdrop can be redeemed uint64 public immutable redeemDeadline; /// @notice Creates the airdrop for the token at address `_token` and `_manager` as the manager. The airdrop can be redeemed until `_redeemDeadline`. /// @param _token The token that should be used for the airdrop /// @param _manager The manager of this airdrop (e.g. the address that can call `initializeRoot`) /// @param _redeemDeadline The deadline until when the airdrop could be redeemed (if inititalized). This needs to be a date in the future. constructor( address _token, address _manager, uint64 _redeemDeadline ) VestingPool(_token, _manager) { require(_redeemDeadline > block.timestamp, "Redeem deadline should be in the future"); redeemDeadline = _redeemDeadline; } /// @notice Initialize the airdrop with `_root` as the Merkle root. /// @dev This can only be called once /// @param _root The Merkle root that should be set for this contract function initializeRoot(bytes32 _root) public onlyPoolManager { require(root == bytes32(0), "State root already initialized"); root = _root; } /// @notice Creates a vesting authorized by the Merkle proof. /// @dev It is required that the pool has enough tokens available /// @dev Vesting will be created for msg.sender /// @param curveType Type of the curve that should be used for the vesting /// @param durationWeeks The duration of the vesting in weeks /// @param startDate The date when the vesting should be started (can be in the past) /// @param amount Amount of tokens that should be vested in atoms /// @param proof Proof to redeem tokens function redeem( uint8 curveType, uint16 durationWeeks, uint64 startDate, uint128 amount, bytes32[] calldata proof ) external { require(block.timestamp <= redeemDeadline, "Deadline to redeem vesting has been exceeded"); require(root != bytes32(0), "State root not initialized"); // This call will fail if the vesting was already created bytes32 vestingId = _addVesting(msg.sender, curveType, false, durationWeeks, startDate, amount); require(MerkleProof.verify(proof, root, vestingId), "Invalid merkle proof"); } /// @notice Claim `tokensToClaim` tokens from vesting `vestingId` and transfer them to the `beneficiary`. /// @dev This can only be called by the owner of the vesting /// @dev Beneficiary cannot be the 0-address /// @dev This will trigger a transfer of tokens via a module transaction /// @param vestingId Id of the vesting from which the tokens should be claimed /// @param beneficiary Account that should receive the claimed tokens /// @param tokensToClaim Amount of tokens to claim in atoms or max uint128 to claim all available function claimVestedTokensViaModule( bytes32 vestingId, address beneficiary, uint128 tokensToClaim ) public { uint128 tokensClaimed = updateClaimedTokens(vestingId, beneficiary, tokensToClaim); // Approve pool manager to transfer tokens on behalf of the pool require(IERC20(token).approve(poolManager, tokensClaimed), "Could not approve tokens"); // Check state prior to transfer uint256 balancePoolBefore = IERC20(token).balanceOf(address(this)); uint256 balanceBeneficiaryBefore = IERC20(token).balanceOf(beneficiary); // Build transfer data to call token contract via the pool manager bytes memory transferData = abi.encodeWithSignature( "transferFrom(address,address,uint256)", address(this), beneficiary, tokensClaimed ); // Trigger transfer of tokens from this pool to the beneficiary via the pool manager as a module transaction require(ModuleManager(poolManager).execTransactionFromModule(token, 0, transferData, 0), "Module transaction failed"); // Set allowance to 0 to avoid any left over allowance. (Note: this should be impossible for normal ERC20 tokens) require(IERC20(token).approve(poolManager, 0), "Could not set token allowance to 0"); // Check state after the transfer uint256 balancePoolAfter = IERC20(token).balanceOf(address(this)); uint256 balanceBeneficiaryAfter = IERC20(token).balanceOf(beneficiary); require(balancePoolAfter == balancePoolBefore - tokensClaimed, "Could not deduct tokens from pool"); require(balanceBeneficiaryAfter == balanceBeneficiaryBefore + tokensClaimed, "Could not add tokens to beneficiary"); } /// @notice Claims all tokens that have not been redeemed before `redeemDeadline` /// @dev Can only be called after `redeemDeadline` has been reached. /// @param beneficiary Account that should receive the claimed tokens function claimUnusedTokens(address beneficiary) external onlyPoolManager { require(block.timestamp > redeemDeadline, "Tokens can still be redeemed"); uint256 unusedTokens = tokensAvailableForVesting(); require(unusedTokens > 0, "No tokens to claim"); require(IERC20(token).transfer(beneficiary, unusedTokens), "Token transfer failed"); } /// @dev This method cannot be called on this contract function addVesting( address, uint8, bool, uint16, uint64, uint128 ) public pure override { revert("This method is not available for this contract"); } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.8.0 <0.9.0; import "./vendor/@openzeppelin/contracts/token/ERC20/IERC20.sol"; /// @title Vesting contract for multiple accounts /// @author Richard Meissner - @rmeissner contract VestingPool { event AddedVesting(bytes32 indexed id, address indexed account); event ClaimedVesting(bytes32 indexed id, address indexed account, address indexed beneficiary); event PausedVesting(bytes32 indexed id); event UnpausedVesting(bytes32 indexed id); event CancelledVesting(bytes32 indexed id); // Sane limits based on: https://eips.ethereum.org/EIPS/eip-1985 // amountClaimed should always be equal to or less than amount // pausingDate should always be equal to or greater than startDate struct Vesting { // First storage slot address account; // 20 bytes uint8 curveType; // 1 byte -> Max 256 different curve types bool managed; // 1 byte uint16 durationWeeks; // 2 bytes -> Max 65536 weeks ~ 1260 years uint64 startDate; // 8 bytes -> Works until year 292278994, but not before 1970 // Second storage slot uint128 amount; // 16 bytes -> Max 3.4e20 tokens (including decimals) uint128 amountClaimed; // 16 bytes -> Max 3.4e20 tokens (including decimals) // Third storage slot uint64 pausingDate; // 8 bytes -> Works until year 292278994, but not before 1970 bool cancelled; // 1 byte } // keccak256( // "EIP712Domain(uint256 chainId,address verifyingContract)" // ); bytes32 private constant DOMAIN_SEPARATOR_TYPEHASH = 0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218; // keccak256( // "Vesting(address account,uint8 curveType,bool managed,uint16 durationWeeks,uint64 startDate,uint128 amount)" // ); bytes32 private constant VESTING_TYPEHASH = 0x43838b5ce9ca440d1ac21b07179a1fdd88aa2175e5ea103f6e37aa6d18ce78ad; address public immutable token; address public immutable poolManager; uint256 public totalTokensInVesting; mapping(bytes32 => Vesting) public vestings; modifier onlyPoolManager() { require(msg.sender == poolManager, "Can only be called by pool manager"); _; } constructor(address _token, address _poolManager) { token = _token; poolManager = _poolManager; } /// @notice Create a vesting on this pool for `account`. /// @dev This can only be called by the pool manager /// @dev It is required that the pool has enough tokens available /// @param account The account for which the vesting is created /// @param curveType Type of the curve that should be used for the vesting /// @param managed Boolean that indicates if the vesting can be managed by the pool manager /// @param durationWeeks The duration of the vesting in weeks /// @param startDate The date when the vesting should be started (can be in the past) /// @param amount Amount of tokens that should be vested in atoms function addVesting( address account, uint8 curveType, bool managed, uint16 durationWeeks, uint64 startDate, uint128 amount ) public virtual onlyPoolManager { _addVesting(account, curveType, managed, durationWeeks, startDate, amount); } /// @notice Calculate the amount of tokens available for new vestings. /// @dev This value changes when more tokens are deposited to this contract /// @return Amount of tokens that can be used for new vestings. function tokensAvailableForVesting() public view virtual returns (uint256) { return IERC20(token).balanceOf(address(this)) - totalTokensInVesting; } /// @notice Create a vesting on this pool for `account`. /// @dev It is required that the pool has enough tokens available /// @dev Account cannot be zero address /// @param account The account for which the vesting is created /// @param curveType Type of the curve that should be used for the vesting /// @param managed Boolean that indicates if the vesting can be managed by the pool manager /// @param durationWeeks The duration of the vesting in weeks /// @param startDate The date when the vesting should be started (can be in the past) /// @param amount Amount of tokens that should be vested in atoms /// @param vestingId The id of the created vesting function _addVesting( address account, uint8 curveType, bool managed, uint16 durationWeeks, uint64 startDate, uint128 amount ) internal returns (bytes32 vestingId) { require(account != address(0), "Invalid account"); require(curveType < 2, "Invalid vesting curve"); vestingId = vestingHash(account, curveType, managed, durationWeeks, startDate, amount); require(vestings[vestingId].account == address(0), "Vesting id already used"); // Check that enough tokens are available for the new vesting uint256 availableTokens = tokensAvailableForVesting(); require(availableTokens >= amount, "Not enough tokens available"); // Mark tokens for this vesting in use totalTokensInVesting += amount; vestings[vestingId] = Vesting({ account: account, curveType: curveType, managed: managed, durationWeeks: durationWeeks, startDate: startDate, amount: amount, amountClaimed: 0, pausingDate: 0, cancelled: false }); emit AddedVesting(vestingId, account); } /// @notice Claim `tokensToClaim` tokens from vesting `vestingId` and transfer them to the `beneficiary`. /// @dev This can only be called by the owner of the vesting /// @dev Beneficiary cannot be the 0-address /// @dev This will trigger a transfer of tokens /// @param vestingId Id of the vesting from which the tokens should be claimed /// @param beneficiary Account that should receive the claimed tokens /// @param tokensToClaim Amount of tokens to claim in atoms or max uint128 to claim all available function claimVestedTokens( bytes32 vestingId, address beneficiary, uint128 tokensToClaim ) public { uint128 tokensClaimed = updateClaimedTokens(vestingId, beneficiary, tokensToClaim); require(IERC20(token).transfer(beneficiary, tokensClaimed), "Token transfer failed"); } /// @notice Update `amountClaimed` on vesting `vestingId` by `tokensToClaim` tokens. /// @dev This can only be called by the owner of the vesting /// @dev Beneficiary cannot be the 0-address /// @dev This will only update the internal state and NOT trigger the transfer of tokens. /// @param vestingId Id of the vesting from which the tokens should be claimed /// @param beneficiary Account that should receive the claimed tokens /// @param tokensToClaim Amount of tokens to claim in atoms or max uint128 to claim all available /// @param tokensClaimed Amount of tokens that have been newly claimed by calling this method function updateClaimedTokens( bytes32 vestingId, address beneficiary, uint128 tokensToClaim ) internal returns (uint128 tokensClaimed) { require(beneficiary != address(0), "Cannot claim to 0-address"); Vesting storage vesting = vestings[vestingId]; require(vesting.account == msg.sender, "Can only be claimed by vesting owner"); // Calculate how many tokens can be claimed uint128 availableClaim = _calculateVestedAmount(vesting) - vesting.amountClaimed; // If max uint128 is used, claim all available tokens. tokensClaimed = tokensToClaim == type(uint128).max ? availableClaim : tokensToClaim; require(tokensClaimed <= availableClaim, "Trying to claim too many tokens"); // Adjust how many tokens are locked in vesting totalTokensInVesting -= tokensClaimed; vesting.amountClaimed += tokensClaimed; emit ClaimedVesting(vestingId, vesting.account, beneficiary); } /// @notice Cancel vesting `vestingId`. /// @dev This can only be called by the pool manager /// @dev Only manageable vestings can be cancelled /// @param vestingId Id of the vesting that should be cancelled function cancelVesting(bytes32 vestingId) public onlyPoolManager { Vesting storage vesting = vestings[vestingId]; require(vesting.account != address(0), "Vesting not found"); require(vesting.managed, "Only managed vestings can be cancelled"); require(!vesting.cancelled, "Vesting already cancelled"); bool isFutureVesting = block.timestamp <= vesting.startDate; // If vesting is not already paused it will be paused // Pausing date should not be reset else tokens of the initial pause can be claimed if (vesting.pausingDate == 0) { // pausingDate should always be larger or equal to startDate vesting.pausingDate = isFutureVesting ? vesting.startDate : uint64(block.timestamp); } // Vesting is cancelled, therefore tokens that are not vested yet, will be added back to the pool uint128 unusedToken = isFutureVesting ? vesting.amount : vesting.amount - _calculateVestedAmount(vesting); totalTokensInVesting -= unusedToken; // Vesting is set to cancelled and therefore disallows unpausing vesting.cancelled = true; emit CancelledVesting(vestingId); } /// @notice Pause vesting `vestingId`. /// @dev This can only be called by the pool manager /// @dev Only manageable vestings can be paused /// @param vestingId Id of the vesting that should be paused function pauseVesting(bytes32 vestingId) public onlyPoolManager { Vesting storage vesting = vestings[vestingId]; require(vesting.account != address(0), "Vesting not found"); require(vesting.managed, "Only managed vestings can be paused"); require(vesting.pausingDate == 0, "Vesting already paused"); // pausingDate should always be larger or equal to startDate vesting.pausingDate = block.timestamp <= vesting.startDate ? vesting.startDate : uint64(block.timestamp); emit PausedVesting(vestingId); } /// @notice Unpause vesting `vestingId`. /// @dev This can only be called by the pool manager /// @dev Only vestings that have not been cancelled can be unpaused /// @param vestingId Id of the vesting that should be unpaused function unpauseVesting(bytes32 vestingId) public onlyPoolManager { Vesting storage vesting = vestings[vestingId]; require(vesting.account != address(0), "Vesting not found"); require(vesting.pausingDate != 0, "Vesting is not paused"); require(!vesting.cancelled, "Vesting has been cancelled and cannot be unpaused"); // Calculate the time the vesting was paused // If vesting has not started yet, then pausing date might be in the future uint64 timePaused = block.timestamp <= vesting.pausingDate ? 0 : uint64(block.timestamp) - vesting.pausingDate; // Offset the start date to create the effect of pausing vesting.startDate = vesting.startDate + timePaused; vesting.pausingDate = 0; emit UnpausedVesting(vestingId); } /// @notice Calculate vested and claimed token amounts for vesting `vestingId`. /// @dev This will revert if the vesting has not been started yet /// @param vestingId Id of the vesting for which to calculate the amounts /// @return vestedAmount The amount in atoms of tokens vested /// @return claimedAmount The amount in atoms of tokens claimed function calculateVestedAmount(bytes32 vestingId) external view returns (uint128 vestedAmount, uint128 claimedAmount) { Vesting storage vesting = vestings[vestingId]; require(vesting.account != address(0), "Vesting not found"); vestedAmount = _calculateVestedAmount(vesting); claimedAmount = vesting.amountClaimed; } /// @notice Calculate vested token amount for vesting `vesting`. /// @dev This will revert if the vesting has not been started yet /// @param vesting The vesting for which to calculate the amounts /// @return vestedAmount The amount in atoms of tokens vested function _calculateVestedAmount(Vesting storage vesting) internal view returns (uint128 vestedAmount) { require(vesting.startDate <= block.timestamp, "Vesting not active yet"); // Convert vesting duration to seconds uint64 durationSeconds = uint64(vesting.durationWeeks) * 7 * 24 * 60 * 60; // If contract is paused use the pausing date to calculate amount uint64 vestedSeconds = vesting.pausingDate > 0 ? vesting.pausingDate - vesting.startDate : uint64(block.timestamp) - vesting.startDate; if (vestedSeconds >= durationSeconds) { // If vesting time is longer than duration everything has been vested vestedAmount = vesting.amount; } else if (vesting.curveType == 0) { // Linear vesting vestedAmount = calculateLinear(vesting.amount, vestedSeconds, durationSeconds); } else if (vesting.curveType == 1) { // Exponential vesting vestedAmount = calculateExponential(vesting.amount, vestedSeconds, durationSeconds); } else { // This is unreachable because it is not possible to add a vesting with an invalid curve type revert("Invalid curve type"); } } /// @notice Calculate vested token amount on a linear curve. /// @dev Calculate vested amount on linear curve: targetAmount * elapsedTime / totalTime /// @param targetAmount Amount of tokens that is being vested /// @param elapsedTime Time that has elapsed for the vesting /// @param totalTime Duration of the vesting /// @return Tokens that have been vested on a linear curve function calculateLinear( uint128 targetAmount, uint64 elapsedTime, uint64 totalTime ) internal pure returns (uint128) { // Calculate vested amount on linear curve: targetAmount * elapsedTime / totalTime uint256 amount = (uint256(targetAmount) * uint256(elapsedTime)) / uint256(totalTime); require(amount <= type(uint128).max, "Overflow in curve calculation"); return uint128(amount); } /// @notice Calculate vested token amount on an exponential curve. /// @dev Calculate vested amount on exponential curve: targetAmount * elapsedTime^2 / totalTime^2 /// @param targetAmount Amount of tokens that is being vested /// @param elapsedTime Time that has elapsed for the vesting /// @param totalTime Duration of the vesting /// @return Tokens that have been vested on an exponential curve function calculateExponential( uint128 targetAmount, uint64 elapsedTime, uint64 totalTime ) internal pure returns (uint128) { // Calculate vested amount on exponential curve: targetAmount * elapsedTime^2 / totalTime^2 uint256 amount = (uint256(targetAmount) * uint256(elapsedTime) * uint256(elapsedTime)) / (uint256(totalTime) * uint256(totalTime)); require(amount <= type(uint128).max, "Overflow in curve calculation"); return uint128(amount); } /// @notice Calculate the id for a vesting based on its parameters. /// @dev The id is a EIP-712 based hash of the vesting. /// @param account The account for which the vesting was created /// @param curveType Type of the curve that is used for the vesting /// @param managed Indicator if the vesting is managed by the pool manager /// @param durationWeeks The duration of the vesting in weeks /// @param startDate The date when the vesting started (can be in the future) /// @param amount Amount of tokens that are vested in atoms /// @return vestingId Id of a vesting based on its parameters function vestingHash( address account, uint8 curveType, bool managed, uint16 durationWeeks, uint64 startDate, uint128 amount ) public view returns (bytes32 vestingId) { bytes32 domainSeparator = keccak256(abi.encode(DOMAIN_SEPARATOR_TYPEHASH, block.chainid, this)); bytes32 vestingDataHash = keccak256(abi.encode(VESTING_TYPEHASH, account, curveType, managed, durationWeeks, startDate, amount)); vestingId = keccak256(abi.encodePacked(bytes1(0x19), bytes1(0x01), domainSeparator, vestingDataHash)); } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.8.0 <0.9.0; interface ModuleManager { /// @notice Allows a module to execute a Safe transaction. /// @dev The implementation of the interface might require that the module is enabled (e.g. for the Safe contracts via enableModule) and could revert otherwise /// @param to Destination address of module transaction. /// @param value Ether value of module transaction. /// @param data Data payload of module transaction. /// @param operation Operation type of module transaction. /// @param success Indicates if the Safe transaction was executed successfully or not function execTransactionFromModule( address to, uint256 value, bytes memory data, uint8 operation ) external returns (bool success); } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/IERC20.sol) 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 `to`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address to, 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 `from` to `to` 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 from, address to, 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 // OpenZeppelin Contracts (last updated v4.5.0) (utils/cryptography/MerkleProof.sol) pragma solidity ^0.8.0; /** * @dev These functions deal with verification of Merkle Trees proofs. * * The proofs can be generated using the JavaScript library * https://github.com/miguelmota/merkletreejs[merkletreejs]. * Note: the hashing algorithm should be keccak256 and pair sorting should be enabled. * * See `test/utils/cryptography/MerkleProof.test.js` for some examples. */ library MerkleProof { /** * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree * defined by `root`. For this, a `proof` must be provided, containing * sibling hashes on the branch from the leaf to the root of the tree. Each * pair of leaves and each pair of pre-images are assumed to be sorted. */ function verify( bytes32[] memory proof, bytes32 root, bytes32 leaf ) internal pure returns (bool) { return processProof(proof, leaf) == root; } /** * @dev Returns the rebuilt hash obtained by traversing a Merklee tree up * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt * hash matches the root of the tree. When processing the proof, the pairs * of leafs & pre-images are assumed to be sorted. * * _Available since v4.4._ */ function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) { bytes32 computedHash = leaf; for (uint256 i = 0; i < proof.length; i++) { bytes32 proofElement = proof[i]; if (computedHash <= proofElement) { // Hash(current computed hash + current element of the proof) computedHash = _efficientHash(computedHash, proofElement); } else { // Hash(current element of the proof + current computed hash) computedHash = _efficientHash(proofElement, computedHash); } } return computedHash; } function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) { assembly { mstore(0x00, a) mstore(0x20, b) value := keccak256(0x00, 0x40) } } }
File 5 of 6: GnosisSafe
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; import "./base/ModuleManager.sol"; import "./base/OwnerManager.sol"; import "./base/FallbackManager.sol"; import "./base/GuardManager.sol"; import "./common/EtherPaymentFallback.sol"; import "./common/Singleton.sol"; import "./common/SignatureDecoder.sol"; import "./common/SecuredTokenTransfer.sol"; import "./common/StorageAccessible.sol"; import "./interfaces/ISignatureValidator.sol"; import "./external/GnosisSafeMath.sol"; /// @title Gnosis Safe - A multisignature wallet with support for confirmations using signed messages based on ERC191. /// @author Stefan George - <[email protected]> /// @author Richard Meissner - <[email protected]> contract GnosisSafe is EtherPaymentFallback, Singleton, ModuleManager, OwnerManager, SignatureDecoder, SecuredTokenTransfer, ISignatureValidatorConstants, FallbackManager, StorageAccessible, GuardManager { using GnosisSafeMath for uint256; string public constant VERSION = "1.3.0"; // keccak256( // "EIP712Domain(uint256 chainId,address verifyingContract)" // ); bytes32 private constant DOMAIN_SEPARATOR_TYPEHASH = 0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218; // keccak256( // "SafeTx(address to,uint256 value,bytes data,uint8 operation,uint256 safeTxGas,uint256 baseGas,uint256 gasPrice,address gasToken,address refundReceiver,uint256 nonce)" // ); bytes32 private constant SAFE_TX_TYPEHASH = 0xbb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d8; event SafeSetup(address indexed initiator, address[] owners, uint256 threshold, address initializer, address fallbackHandler); event ApproveHash(bytes32 indexed approvedHash, address indexed owner); event SignMsg(bytes32 indexed msgHash); event ExecutionFailure(bytes32 txHash, uint256 payment); event ExecutionSuccess(bytes32 txHash, uint256 payment); uint256 public nonce; bytes32 private _deprecatedDomainSeparator; // Mapping to keep track of all message hashes that have been approve by ALL REQUIRED owners mapping(bytes32 => uint256) public signedMessages; // Mapping to keep track of all hashes (message or transaction) that have been approve by ANY owners mapping(address => mapping(bytes32 => uint256)) public approvedHashes; // This constructor ensures that this contract can only be used as a master copy for Proxy contracts constructor() { // By setting the threshold it is not possible to call setup anymore, // so we create a Safe with 0 owners and threshold 1. // This is an unusable Safe, perfect for the singleton threshold = 1; } /// @dev Setup function sets initial storage of contract. /// @param _owners List of Safe owners. /// @param _threshold Number of required confirmations for a Safe transaction. /// @param to Contract address for optional delegate call. /// @param data Data payload for optional delegate call. /// @param fallbackHandler Handler for fallback calls to this contract /// @param paymentToken Token that should be used for the payment (0 is ETH) /// @param payment Value that should be paid /// @param paymentReceiver Adddress that should receive the payment (or 0 if tx.origin) function setup( address[] calldata _owners, uint256 _threshold, address to, bytes calldata data, address fallbackHandler, address paymentToken, uint256 payment, address payable paymentReceiver ) external { // setupOwners checks if the Threshold is already set, therefore preventing that this method is called twice setupOwners(_owners, _threshold); if (fallbackHandler != address(0)) internalSetFallbackHandler(fallbackHandler); // As setupOwners can only be called if the contract has not been initialized we don't need a check for setupModules setupModules(to, data); if (payment > 0) { // To avoid running into issues with EIP-170 we reuse the handlePayment function (to avoid adjusting code of that has been verified we do not adjust the method itself) // baseGas = 0, gasPrice = 1 and gas = payment => amount = (payment + 0) * 1 = payment handlePayment(payment, 0, 1, paymentToken, paymentReceiver); } emit SafeSetup(msg.sender, _owners, _threshold, to, fallbackHandler); } /// @dev Allows to execute a Safe transaction confirmed by required number of owners and then pays the account that submitted the transaction. /// Note: The fees are always transferred, even if the user transaction fails. /// @param to Destination address of Safe transaction. /// @param value Ether value of Safe transaction. /// @param data Data payload of Safe transaction. /// @param operation Operation type of Safe transaction. /// @param safeTxGas Gas that should be used for the Safe transaction. /// @param baseGas Gas costs that are independent of the transaction execution(e.g. base transaction fee, signature check, payment of the refund) /// @param gasPrice Gas price that should be used for the payment calculation. /// @param gasToken Token address (or 0 if ETH) that is used for the payment. /// @param refundReceiver Address of receiver of gas payment (or 0 if tx.origin). /// @param signatures Packed signature data ({bytes32 r}{bytes32 s}{uint8 v}) function execTransaction( address to, uint256 value, bytes calldata data, Enum.Operation operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address payable refundReceiver, bytes memory signatures ) public payable virtual returns (bool success) { bytes32 txHash; // Use scope here to limit variable lifetime and prevent `stack too deep` errors { bytes memory txHashData = encodeTransactionData( // Transaction info to, value, data, operation, safeTxGas, // Payment info baseGas, gasPrice, gasToken, refundReceiver, // Signature info nonce ); // Increase nonce and execute transaction. nonce++; txHash = keccak256(txHashData); checkSignatures(txHash, txHashData, signatures); } address guard = getGuard(); { if (guard != address(0)) { Guard(guard).checkTransaction( // Transaction info to, value, data, operation, safeTxGas, // Payment info baseGas, gasPrice, gasToken, refundReceiver, // Signature info signatures, msg.sender ); } } // We require some gas to emit the events (at least 2500) after the execution and some to perform code until the execution (500) // We also include the 1/64 in the check that is not send along with a call to counteract potential shortings because of EIP-150 require(gasleft() >= ((safeTxGas * 64) / 63).max(safeTxGas + 2500) + 500, "GS010"); // Use scope here to limit variable lifetime and prevent `stack too deep` errors { uint256 gasUsed = gasleft(); // If the gasPrice is 0 we assume that nearly all available gas can be used (it is always more than safeTxGas) // We only substract 2500 (compared to the 3000 before) to ensure that the amount passed is still higher than safeTxGas success = execute(to, value, data, operation, gasPrice == 0 ? (gasleft() - 2500) : safeTxGas); gasUsed = gasUsed.sub(gasleft()); // If no safeTxGas and no gasPrice was set (e.g. both are 0), then the internal tx is required to be successful // This makes it possible to use `estimateGas` without issues, as it searches for the minimum gas where the tx doesn't revert require(success || safeTxGas != 0 || gasPrice != 0, "GS013"); // We transfer the calculated tx costs to the tx.origin to avoid sending it to intermediate contracts that have made calls uint256 payment = 0; if (gasPrice > 0) { payment = handlePayment(gasUsed, baseGas, gasPrice, gasToken, refundReceiver); } if (success) emit ExecutionSuccess(txHash, payment); else emit ExecutionFailure(txHash, payment); } { if (guard != address(0)) { Guard(guard).checkAfterExecution(txHash, success); } } } function handlePayment( uint256 gasUsed, uint256 baseGas, uint256 gasPrice, address gasToken, address payable refundReceiver ) private returns (uint256 payment) { // solhint-disable-next-line avoid-tx-origin address payable receiver = refundReceiver == address(0) ? payable(tx.origin) : refundReceiver; if (gasToken == address(0)) { // For ETH we will only adjust the gas price to not be higher than the actual used gas price payment = gasUsed.add(baseGas).mul(gasPrice < tx.gasprice ? gasPrice : tx.gasprice); require(receiver.send(payment), "GS011"); } else { payment = gasUsed.add(baseGas).mul(gasPrice); require(transferToken(gasToken, receiver, payment), "GS012"); } } /** * @dev Checks whether the signature provided is valid for the provided data, hash. Will revert otherwise. * @param dataHash Hash of the data (could be either a message hash or transaction hash) * @param data That should be signed (this is passed to an external validator contract) * @param signatures Signature data that should be verified. Can be ECDSA signature, contract signature (EIP-1271) or approved hash. */ function checkSignatures( bytes32 dataHash, bytes memory data, bytes memory signatures ) public view { // Load threshold to avoid multiple storage loads uint256 _threshold = threshold; // Check that a threshold is set require(_threshold > 0, "GS001"); checkNSignatures(dataHash, data, signatures, _threshold); } /** * @dev Checks whether the signature provided is valid for the provided data, hash. Will revert otherwise. * @param dataHash Hash of the data (could be either a message hash or transaction hash) * @param data That should be signed (this is passed to an external validator contract) * @param signatures Signature data that should be verified. Can be ECDSA signature, contract signature (EIP-1271) or approved hash. * @param requiredSignatures Amount of required valid signatures. */ function checkNSignatures( bytes32 dataHash, bytes memory data, bytes memory signatures, uint256 requiredSignatures ) public view { // Check that the provided signature data is not too short require(signatures.length >= requiredSignatures.mul(65), "GS020"); // There cannot be an owner with address 0. address lastOwner = address(0); address currentOwner; uint8 v; bytes32 r; bytes32 s; uint256 i; for (i = 0; i < requiredSignatures; i++) { (v, r, s) = signatureSplit(signatures, i); if (v == 0) { // If v is 0 then it is a contract signature // When handling contract signatures the address of the contract is encoded into r currentOwner = address(uint160(uint256(r))); // Check that signature data pointer (s) is not pointing inside the static part of the signatures bytes // This check is not completely accurate, since it is possible that more signatures than the threshold are send. // Here we only check that the pointer is not pointing inside the part that is being processed require(uint256(s) >= requiredSignatures.mul(65), "GS021"); // Check that signature data pointer (s) is in bounds (points to the length of data -> 32 bytes) require(uint256(s).add(32) <= signatures.length, "GS022"); // Check if the contract signature is in bounds: start of data is s + 32 and end is start + signature length uint256 contractSignatureLen; // solhint-disable-next-line no-inline-assembly assembly { contractSignatureLen := mload(add(add(signatures, s), 0x20)) } require(uint256(s).add(32).add(contractSignatureLen) <= signatures.length, "GS023"); // Check signature bytes memory contractSignature; // solhint-disable-next-line no-inline-assembly assembly { // The signature data for contract signatures is appended to the concatenated signatures and the offset is stored in s contractSignature := add(add(signatures, s), 0x20) } require(ISignatureValidator(currentOwner).isValidSignature(data, contractSignature) == EIP1271_MAGIC_VALUE, "GS024"); } else if (v == 1) { // If v is 1 then it is an approved hash // When handling approved hashes the address of the approver is encoded into r currentOwner = address(uint160(uint256(r))); // Hashes are automatically approved by the sender of the message or when they have been pre-approved via a separate transaction require(msg.sender == currentOwner || approvedHashes[currentOwner][dataHash] != 0, "GS025"); } else if (v > 30) { // If v > 30 then default va (27,28) has been adjusted for eth_sign flow // To support eth_sign and similar we adjust v and hash the messageHash with the Ethereum message prefix before applying ecrecover currentOwner = ecrecover(keccak256(abi.encodePacked("\\x19Ethereum Signed Message:\ 32", dataHash)), v - 4, r, s); } else { // Default is the ecrecover flow with the provided data hash // Use ecrecover with the messageHash for EOA signatures currentOwner = ecrecover(dataHash, v, r, s); } require(currentOwner > lastOwner && owners[currentOwner] != address(0) && currentOwner != SENTINEL_OWNERS, "GS026"); lastOwner = currentOwner; } } /// @dev Allows to estimate a Safe transaction. /// This method is only meant for estimation purpose, therefore the call will always revert and encode the result in the revert data. /// Since the `estimateGas` function includes refunds, call this method to get an estimated of the costs that are deducted from the safe with `execTransaction` /// @param to Destination address of Safe transaction. /// @param value Ether value of Safe transaction. /// @param data Data payload of Safe transaction. /// @param operation Operation type of Safe transaction. /// @return Estimate without refunds and overhead fees (base transaction and payload data gas costs). /// @notice Deprecated in favor of common/StorageAccessible.sol and will be removed in next version. function requiredTxGas( address to, uint256 value, bytes calldata data, Enum.Operation operation ) external returns (uint256) { uint256 startGas = gasleft(); // We don't provide an error message here, as we use it to return the estimate require(execute(to, value, data, operation, gasleft())); uint256 requiredGas = startGas - gasleft(); // Convert response to string and return via error message revert(string(abi.encodePacked(requiredGas))); } /** * @dev Marks a hash as approved. This can be used to validate a hash that is used by a signature. * @param hashToApprove The hash that should be marked as approved for signatures that are verified by this contract. */ function approveHash(bytes32 hashToApprove) external { require(owners[msg.sender] != address(0), "GS030"); approvedHashes[msg.sender][hashToApprove] = 1; emit ApproveHash(hashToApprove, msg.sender); } /// @dev Returns the chain id used by this contract. function getChainId() public view returns (uint256) { uint256 id; // solhint-disable-next-line no-inline-assembly assembly { id := chainid() } return id; } function domainSeparator() public view returns (bytes32) { return keccak256(abi.encode(DOMAIN_SEPARATOR_TYPEHASH, getChainId(), this)); } /// @dev Returns the bytes that are hashed to be signed by owners. /// @param to Destination address. /// @param value Ether value. /// @param data Data payload. /// @param operation Operation type. /// @param safeTxGas Gas that should be used for the safe transaction. /// @param baseGas Gas costs for that are independent of the transaction execution(e.g. base transaction fee, signature check, payment of the refund) /// @param gasPrice Maximum gas price that should be used for this transaction. /// @param gasToken Token address (or 0 if ETH) that is used for the payment. /// @param refundReceiver Address of receiver of gas payment (or 0 if tx.origin). /// @param _nonce Transaction nonce. /// @return Transaction hash bytes. function encodeTransactionData( address to, uint256 value, bytes calldata data, Enum.Operation operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address refundReceiver, uint256 _nonce ) public view returns (bytes memory) { bytes32 safeTxHash = keccak256( abi.encode( SAFE_TX_TYPEHASH, to, value, keccak256(data), operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, _nonce ) ); return abi.encodePacked(bytes1(0x19), bytes1(0x01), domainSeparator(), safeTxHash); } /// @dev Returns hash to be signed by owners. /// @param to Destination address. /// @param value Ether value. /// @param data Data payload. /// @param operation Operation type. /// @param safeTxGas Fas that should be used for the safe transaction. /// @param baseGas Gas costs for data used to trigger the safe transaction. /// @param gasPrice Maximum gas price that should be used for this transaction. /// @param gasToken Token address (or 0 if ETH) that is used for the payment. /// @param refundReceiver Address of receiver of gas payment (or 0 if tx.origin). /// @param _nonce Transaction nonce. /// @return Transaction hash. function getTransactionHash( address to, uint256 value, bytes calldata data, Enum.Operation operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address refundReceiver, uint256 _nonce ) public view returns (bytes32) { return keccak256(encodeTransactionData(to, value, data, operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, _nonce)); } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; import "../common/Enum.sol"; /// @title Executor - A contract that can execute transactions /// @author Richard Meissner - <[email protected]> contract Executor { function execute( address to, uint256 value, bytes memory data, Enum.Operation operation, uint256 txGas ) internal returns (bool success) { if (operation == Enum.Operation.DelegateCall) { // solhint-disable-next-line no-inline-assembly assembly { success := delegatecall(txGas, to, add(data, 0x20), mload(data), 0, 0) } } else { // solhint-disable-next-line no-inline-assembly assembly { success := call(txGas, to, value, add(data, 0x20), mload(data), 0, 0) } } } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; import "../common/SelfAuthorized.sol"; /// @title Fallback Manager - A contract that manages fallback calls made to this contract /// @author Richard Meissner - <[email protected]> contract FallbackManager is SelfAuthorized { event ChangedFallbackHandler(address handler); // keccak256("fallback_manager.handler.address") bytes32 internal constant FALLBACK_HANDLER_STORAGE_SLOT = 0x6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d5; function internalSetFallbackHandler(address handler) internal { bytes32 slot = FALLBACK_HANDLER_STORAGE_SLOT; // solhint-disable-next-line no-inline-assembly assembly { sstore(slot, handler) } } /// @dev Allows to add a contract to handle fallback calls. /// Only fallback calls without value and with data will be forwarded. /// This can only be done via a Safe transaction. /// @param handler contract to handle fallbacks calls. function setFallbackHandler(address handler) public authorized { internalSetFallbackHandler(handler); emit ChangedFallbackHandler(handler); } // solhint-disable-next-line payable-fallback,no-complex-fallback fallback() external { bytes32 slot = FALLBACK_HANDLER_STORAGE_SLOT; // solhint-disable-next-line no-inline-assembly assembly { let handler := sload(slot) if iszero(handler) { return(0, 0) } calldatacopy(0, 0, calldatasize()) // The msg.sender address is shifted to the left by 12 bytes to remove the padding // Then the address without padding is stored right after the calldata mstore(calldatasize(), shl(96, caller())) // Add 20 bytes for the address appended add the end let success := call(gas(), handler, 0, 0, add(calldatasize(), 20), 0, 0) returndatacopy(0, 0, returndatasize()) if iszero(success) { revert(0, returndatasize()) } return(0, returndatasize()) } } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; import "../common/Enum.sol"; import "../common/SelfAuthorized.sol"; interface Guard { function checkTransaction( address to, uint256 value, bytes memory data, Enum.Operation operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address payable refundReceiver, bytes memory signatures, address msgSender ) external; function checkAfterExecution(bytes32 txHash, bool success) external; } /// @title Fallback Manager - A contract that manages fallback calls made to this contract /// @author Richard Meissner - <[email protected]> contract GuardManager is SelfAuthorized { event ChangedGuard(address guard); // keccak256("guard_manager.guard.address") bytes32 internal constant GUARD_STORAGE_SLOT = 0x4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8; /// @dev Set a guard that checks transactions before execution /// @param guard The address of the guard to be used or the 0 address to disable the guard function setGuard(address guard) external authorized { bytes32 slot = GUARD_STORAGE_SLOT; // solhint-disable-next-line no-inline-assembly assembly { sstore(slot, guard) } emit ChangedGuard(guard); } function getGuard() internal view returns (address guard) { bytes32 slot = GUARD_STORAGE_SLOT; // solhint-disable-next-line no-inline-assembly assembly { guard := sload(slot) } } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; import "../common/Enum.sol"; import "../common/SelfAuthorized.sol"; import "./Executor.sol"; /// @title Module Manager - A contract that manages modules that can execute transactions via this contract /// @author Stefan George - <[email protected]> /// @author Richard Meissner - <[email protected]> contract ModuleManager is SelfAuthorized, Executor { event EnabledModule(address module); event DisabledModule(address module); event ExecutionFromModuleSuccess(address indexed module); event ExecutionFromModuleFailure(address indexed module); address internal constant SENTINEL_MODULES = address(0x1); mapping(address => address) internal modules; function setupModules(address to, bytes memory data) internal { require(modules[SENTINEL_MODULES] == address(0), "GS100"); modules[SENTINEL_MODULES] = SENTINEL_MODULES; if (to != address(0)) // Setup has to complete successfully or transaction fails. require(execute(to, 0, data, Enum.Operation.DelegateCall, gasleft()), "GS000"); } /// @dev Allows to add a module to the whitelist. /// This can only be done via a Safe transaction. /// @notice Enables the module `module` for the Safe. /// @param module Module to be whitelisted. function enableModule(address module) public authorized { // Module address cannot be null or sentinel. require(module != address(0) && module != SENTINEL_MODULES, "GS101"); // Module cannot be added twice. require(modules[module] == address(0), "GS102"); modules[module] = modules[SENTINEL_MODULES]; modules[SENTINEL_MODULES] = module; emit EnabledModule(module); } /// @dev Allows to remove a module from the whitelist. /// This can only be done via a Safe transaction. /// @notice Disables the module `module` for the Safe. /// @param prevModule Module that pointed to the module to be removed in the linked list /// @param module Module to be removed. function disableModule(address prevModule, address module) public authorized { // Validate module address and check that it corresponds to module index. require(module != address(0) && module != SENTINEL_MODULES, "GS101"); require(modules[prevModule] == module, "GS103"); modules[prevModule] = modules[module]; modules[module] = address(0); emit DisabledModule(module); } /// @dev Allows a Module to execute a Safe transaction without any further confirmations. /// @param to Destination address of module transaction. /// @param value Ether value of module transaction. /// @param data Data payload of module transaction. /// @param operation Operation type of module transaction. function execTransactionFromModule( address to, uint256 value, bytes memory data, Enum.Operation operation ) public virtual returns (bool success) { // Only whitelisted modules are allowed. require(msg.sender != SENTINEL_MODULES && modules[msg.sender] != address(0), "GS104"); // Execute transaction without further confirmations. success = execute(to, value, data, operation, gasleft()); if (success) emit ExecutionFromModuleSuccess(msg.sender); else emit ExecutionFromModuleFailure(msg.sender); } /// @dev Allows a Module to execute a Safe transaction without any further confirmations and return data /// @param to Destination address of module transaction. /// @param value Ether value of module transaction. /// @param data Data payload of module transaction. /// @param operation Operation type of module transaction. function execTransactionFromModuleReturnData( address to, uint256 value, bytes memory data, Enum.Operation operation ) public returns (bool success, bytes memory returnData) { success = execTransactionFromModule(to, value, data, operation); // solhint-disable-next-line no-inline-assembly assembly { // Load free memory location let ptr := mload(0x40) // We allocate memory for the return data by setting the free memory location to // current free memory location + data size + 32 bytes for data size value mstore(0x40, add(ptr, add(returndatasize(), 0x20))) // Store the size mstore(ptr, returndatasize()) // Store the data returndatacopy(add(ptr, 0x20), 0, returndatasize()) // Point the return data to the correct memory location returnData := ptr } } /// @dev Returns if an module is enabled /// @return True if the module is enabled function isModuleEnabled(address module) public view returns (bool) { return SENTINEL_MODULES != module && modules[module] != address(0); } /// @dev Returns array of modules. /// @param start Start of the page. /// @param pageSize Maximum number of modules that should be returned. /// @return array Array of modules. /// @return next Start of the next page. function getModulesPaginated(address start, uint256 pageSize) external view returns (address[] memory array, address next) { // Init array with max page size array = new address[](pageSize); // Populate return array uint256 moduleCount = 0; address currentModule = modules[start]; while (currentModule != address(0x0) && currentModule != SENTINEL_MODULES && moduleCount < pageSize) { array[moduleCount] = currentModule; currentModule = modules[currentModule]; moduleCount++; } next = currentModule; // Set correct size of returned array // solhint-disable-next-line no-inline-assembly assembly { mstore(array, moduleCount) } } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; import "../common/SelfAuthorized.sol"; /// @title OwnerManager - Manages a set of owners and a threshold to perform actions. /// @author Stefan George - <[email protected]> /// @author Richard Meissner - <[email protected]> contract OwnerManager is SelfAuthorized { event AddedOwner(address owner); event RemovedOwner(address owner); event ChangedThreshold(uint256 threshold); address internal constant SENTINEL_OWNERS = address(0x1); mapping(address => address) internal owners; uint256 internal ownerCount; uint256 internal threshold; /// @dev Setup function sets initial storage of contract. /// @param _owners List of Safe owners. /// @param _threshold Number of required confirmations for a Safe transaction. function setupOwners(address[] memory _owners, uint256 _threshold) internal { // Threshold can only be 0 at initialization. // Check ensures that setup function can only be called once. require(threshold == 0, "GS200"); // Validate that threshold is smaller than number of added owners. require(_threshold <= _owners.length, "GS201"); // There has to be at least one Safe owner. require(_threshold >= 1, "GS202"); // Initializing Safe owners. address currentOwner = SENTINEL_OWNERS; for (uint256 i = 0; i < _owners.length; i++) { // Owner address cannot be null. address owner = _owners[i]; require(owner != address(0) && owner != SENTINEL_OWNERS && owner != address(this) && currentOwner != owner, "GS203"); // No duplicate owners allowed. require(owners[owner] == address(0), "GS204"); owners[currentOwner] = owner; currentOwner = owner; } owners[currentOwner] = SENTINEL_OWNERS; ownerCount = _owners.length; threshold = _threshold; } /// @dev Allows to add a new owner to the Safe and update the threshold at the same time. /// This can only be done via a Safe transaction. /// @notice Adds the owner `owner` to the Safe and updates the threshold to `_threshold`. /// @param owner New owner address. /// @param _threshold New threshold. function addOwnerWithThreshold(address owner, uint256 _threshold) public authorized { // Owner address cannot be null, the sentinel or the Safe itself. require(owner != address(0) && owner != SENTINEL_OWNERS && owner != address(this), "GS203"); // No duplicate owners allowed. require(owners[owner] == address(0), "GS204"); owners[owner] = owners[SENTINEL_OWNERS]; owners[SENTINEL_OWNERS] = owner; ownerCount++; emit AddedOwner(owner); // Change threshold if threshold was changed. if (threshold != _threshold) changeThreshold(_threshold); } /// @dev Allows to remove an owner from the Safe and update the threshold at the same time. /// This can only be done via a Safe transaction. /// @notice Removes the owner `owner` from the Safe and updates the threshold to `_threshold`. /// @param prevOwner Owner that pointed to the owner to be removed in the linked list /// @param owner Owner address to be removed. /// @param _threshold New threshold. function removeOwner( address prevOwner, address owner, uint256 _threshold ) public authorized { // Only allow to remove an owner, if threshold can still be reached. require(ownerCount - 1 >= _threshold, "GS201"); // Validate owner address and check that it corresponds to owner index. require(owner != address(0) && owner != SENTINEL_OWNERS, "GS203"); require(owners[prevOwner] == owner, "GS205"); owners[prevOwner] = owners[owner]; owners[owner] = address(0); ownerCount--; emit RemovedOwner(owner); // Change threshold if threshold was changed. if (threshold != _threshold) changeThreshold(_threshold); } /// @dev Allows to swap/replace an owner from the Safe with another address. /// This can only be done via a Safe transaction. /// @notice Replaces the owner `oldOwner` in the Safe with `newOwner`. /// @param prevOwner Owner that pointed to the owner to be replaced in the linked list /// @param oldOwner Owner address to be replaced. /// @param newOwner New owner address. function swapOwner( address prevOwner, address oldOwner, address newOwner ) public authorized { // Owner address cannot be null, the sentinel or the Safe itself. require(newOwner != address(0) && newOwner != SENTINEL_OWNERS && newOwner != address(this), "GS203"); // No duplicate owners allowed. require(owners[newOwner] == address(0), "GS204"); // Validate oldOwner address and check that it corresponds to owner index. require(oldOwner != address(0) && oldOwner != SENTINEL_OWNERS, "GS203"); require(owners[prevOwner] == oldOwner, "GS205"); owners[newOwner] = owners[oldOwner]; owners[prevOwner] = newOwner; owners[oldOwner] = address(0); emit RemovedOwner(oldOwner); emit AddedOwner(newOwner); } /// @dev Allows to update the number of required confirmations by Safe owners. /// This can only be done via a Safe transaction. /// @notice Changes the threshold of the Safe to `_threshold`. /// @param _threshold New threshold. function changeThreshold(uint256 _threshold) public authorized { // Validate that threshold is smaller than number of owners. require(_threshold <= ownerCount, "GS201"); // There has to be at least one Safe owner. require(_threshold >= 1, "GS202"); threshold = _threshold; emit ChangedThreshold(threshold); } function getThreshold() public view returns (uint256) { return threshold; } function isOwner(address owner) public view returns (bool) { return owner != SENTINEL_OWNERS && owners[owner] != address(0); } /// @dev Returns array of owners. /// @return Array of Safe owners. function getOwners() public view returns (address[] memory) { address[] memory array = new address[](ownerCount); // populate return array uint256 index = 0; address currentOwner = owners[SENTINEL_OWNERS]; while (currentOwner != SENTINEL_OWNERS) { array[index] = currentOwner; currentOwner = owners[currentOwner]; index++; } return array; } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title Enum - Collection of enums /// @author Richard Meissner - <[email protected]> contract Enum { enum Operation {Call, DelegateCall} } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title EtherPaymentFallback - A contract that has a fallback to accept ether payments /// @author Richard Meissner - <[email protected]> contract EtherPaymentFallback { event SafeReceived(address indexed sender, uint256 value); /// @dev Fallback function accepts Ether transactions. receive() external payable { emit SafeReceived(msg.sender, msg.value); } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title SecuredTokenTransfer - Secure token transfer /// @author Richard Meissner - <[email protected]> contract SecuredTokenTransfer { /// @dev Transfers a token and returns if it was a success /// @param token Token that should be transferred /// @param receiver Receiver to whom the token should be transferred /// @param amount The amount of tokens that should be transferred function transferToken( address token, address receiver, uint256 amount ) internal returns (bool transferred) { // 0xa9059cbb - keccack("transfer(address,uint256)") bytes memory data = abi.encodeWithSelector(0xa9059cbb, receiver, amount); // solhint-disable-next-line no-inline-assembly assembly { // We write the return value to scratch space. // See https://docs.soliditylang.org/en/v0.7.6/internals/layout_in_memory.html#layout-in-memory let success := call(sub(gas(), 10000), token, 0, add(data, 0x20), mload(data), 0, 0x20) switch returndatasize() case 0 { transferred := success } case 0x20 { transferred := iszero(or(iszero(success), iszero(mload(0)))) } default { transferred := 0 } } } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title SelfAuthorized - authorizes current contract to perform actions /// @author Richard Meissner - <[email protected]> contract SelfAuthorized { function requireSelfCall() private view { require(msg.sender == address(this), "GS031"); } modifier authorized() { // This is a function call as it minimized the bytecode size requireSelfCall(); _; } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title SignatureDecoder - Decodes signatures that a encoded as bytes /// @author Richard Meissner - <[email protected]> contract SignatureDecoder { /// @dev divides bytes signature into `uint8 v, bytes32 r, bytes32 s`. /// @notice Make sure to peform a bounds check for @param pos, to avoid out of bounds access on @param signatures /// @param pos which signature to read. A prior bounds check of this parameter should be performed, to avoid out of bounds access /// @param signatures concatenated rsv signatures function signatureSplit(bytes memory signatures, uint256 pos) internal pure returns ( uint8 v, bytes32 r, bytes32 s ) { // The signature format is a compact form of: // {bytes32 r}{bytes32 s}{uint8 v} // Compact means, uint8 is not padded to 32 bytes. // solhint-disable-next-line no-inline-assembly assembly { let signaturePos := mul(0x41, pos) r := mload(add(signatures, add(signaturePos, 0x20))) s := mload(add(signatures, add(signaturePos, 0x40))) // Here we are loading the last 32 bytes, including 31 bytes // of 's'. There is no 'mload8' to do this. // // 'byte' is not working due to the Solidity parser, so lets // use the second best option, 'and' v := and(mload(add(signatures, add(signaturePos, 0x41))), 0xff) } } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title Singleton - Base for singleton contracts (should always be first super contract) /// This contract is tightly coupled to our proxy contract (see `proxies/GnosisSafeProxy.sol`) /// @author Richard Meissner - <[email protected]> contract Singleton { // singleton always needs to be first declared variable, to ensure that it is at the same location as in the Proxy contract. // It should also always be ensured that the address is stored alone (uses a full word) address private singleton; } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title StorageAccessible - generic base contract that allows callers to access all internal storage. /// @notice See https://github.com/gnosis/util-contracts/blob/bb5fe5fb5df6d8400998094fb1b32a178a47c3a1/contracts/StorageAccessible.sol contract StorageAccessible { /** * @dev Reads `length` bytes of storage in the currents contract * @param offset - the offset in the current contract's storage in words to start reading from * @param length - the number of words (32 bytes) of data to read * @return the bytes that were read. */ function getStorageAt(uint256 offset, uint256 length) public view returns (bytes memory) { bytes memory result = new bytes(length * 32); for (uint256 index = 0; index < length; index++) { // solhint-disable-next-line no-inline-assembly assembly { let word := sload(add(offset, index)) mstore(add(add(result, 0x20), mul(index, 0x20)), word) } } return result; } /** * @dev Performs a delegetecall on a targetContract in the context of self. * Internally reverts execution to avoid side effects (making it static). * * This method reverts with data equal to `abi.encode(bool(success), bytes(response))`. * Specifically, the `returndata` after a call to this method will be: * `success:bool || response.length:uint256 || response:bytes`. * * @param targetContract Address of the contract containing the code to execute. * @param calldataPayload Calldata that should be sent to the target contract (encoded method name and arguments). */ function simulateAndRevert(address targetContract, bytes memory calldataPayload) external { // solhint-disable-next-line no-inline-assembly assembly { let success := delegatecall(gas(), targetContract, add(calldataPayload, 0x20), mload(calldataPayload), 0, 0) mstore(0x00, success) mstore(0x20, returndatasize()) returndatacopy(0x40, 0, returndatasize()) revert(0, add(returndatasize(), 0x40)) } } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /** * @title GnosisSafeMath * @dev Math operations with safety checks that revert on error * Renamed from SafeMath to GnosisSafeMath to avoid conflicts * TODO: remove once open zeppelin update to solc 0.5.0 */ library GnosisSafeMath { /** * @dev Multiplies two numbers, reverts on 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-solidity/pull/522 if (a == 0) { return 0; } uint256 c = a * b; require(c / a == b); return c; } /** * @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend). */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { require(b <= a); uint256 c = a - b; return c; } /** * @dev Adds two numbers, reverts on overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a); return c; } /** * @dev Returns the largest of two numbers. */ function max(uint256 a, uint256 b) internal pure returns (uint256) { return a >= b ? a : b; } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; contract ISignatureValidatorConstants { // bytes4(keccak256("isValidSignature(bytes,bytes)") bytes4 internal constant EIP1271_MAGIC_VALUE = 0x20c13b0b; } abstract contract ISignatureValidator is ISignatureValidatorConstants { /** * @dev Should return whether the signature provided is valid for the provided data * @param _data Arbitrary length data signed on the behalf of address(this) * @param _signature Signature byte array associated with _data * * MUST return the bytes4 magic value 0x20c13b0b when function passes. * MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5) * MUST allow external calls */ function isValidSignature(bytes memory _data, bytes memory _signature) public view virtual returns (bytes4); }
File 6 of 6: MultiSendCallOnly
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title Multi Send Call Only - Allows to batch multiple transactions into one, but only calls /// @author Stefan George - <[email protected]> /// @author Richard Meissner - <[email protected]> /// @notice The guard logic is not required here as this contract doesn't support nested delegate calls contract MultiSendCallOnly { /// @dev Sends multiple transactions and reverts all if one fails. /// @param transactions Encoded transactions. Each transaction is encoded as a packed bytes of /// operation has to be uint8(0) in this version (=> 1 byte), /// to as a address (=> 20 bytes), /// value as a uint256 (=> 32 bytes), /// data length as a uint256 (=> 32 bytes), /// data as bytes. /// see abi.encodePacked for more information on packed encoding /// @notice The code is for most part the same as the normal MultiSend (to keep compatibility), /// but reverts if a transaction tries to use a delegatecall. /// @notice This method is payable as delegatecalls keep the msg.value from the previous call /// If the calling method (e.g. execTransaction) received ETH this would revert otherwise function multiSend(bytes memory transactions) public payable { // solhint-disable-next-line no-inline-assembly assembly { let length := mload(transactions) let i := 0x20 for { // Pre block is not used in "while mode" } lt(i, length) { // Post block is not used in "while mode" } { // First byte of the data is the operation. // We shift by 248 bits (256 - 8 [operation byte]) it right since mload will always load 32 bytes (a word). // This will also zero out unused data. let operation := shr(0xf8, mload(add(transactions, i))) // We offset the load address by 1 byte (operation byte) // We shift it right by 96 bits (256 - 160 [20 address bytes]) to right-align the data and zero out unused data. let to := shr(0x60, mload(add(transactions, add(i, 0x01)))) // We offset the load address by 21 byte (operation byte + 20 address bytes) let value := mload(add(transactions, add(i, 0x15))) // We offset the load address by 53 byte (operation byte + 20 address bytes + 32 value bytes) let dataLength := mload(add(transactions, add(i, 0x35))) // We offset the load address by 85 byte (operation byte + 20 address bytes + 32 value bytes + 32 data length bytes) let data := add(transactions, add(i, 0x55)) let success := 0 switch operation case 0 { success := call(gas(), to, value, data, dataLength, 0, 0) } // This version does not allow delegatecalls case 1 { revert(0, 0) } if eq(success, 0) { revert(0, 0) } // Next entry starts at 85 byte + data length i := add(i, add(0x55, dataLength)) } } } }