├── reports ├── readme.md ├── 202102 - Slingshot.pdf └── 202103 - ElasticDAO.pdf ├── images └── c4-logo.png ├── SUBMISSION_POLICY.md ├── contests ├── README.md ├── 01-slingshot │ ├── contracts │ │ ├── lib │ │ │ ├── LibERC20Token.sol │ │ │ └── Strings.sol │ │ ├── Adminable.sol │ │ ├── module │ │ │ ├── CurveModule.sol │ │ │ ├── BalancerModule.sol │ │ │ ├── UniswapModule.sol │ │ │ └── SushiSwapModule.sol │ │ ├── ModuleRegistry.sol │ │ ├── Initializable.sol │ │ └── Slingshot.sol │ └── README.md └── 02-elasticdao │ ├── contracts │ ├── services │ │ ├── ReentryProtection.sol │ │ └── Configurator.sol │ ├── interfaces │ │ └── IElasticToken.sol │ ├── models │ │ ├── TokenHolder.sol │ │ ├── EternalModel.sol │ │ ├── DAO.sol │ │ ├── Ecosystem.sol │ │ └── Token.sol │ ├── libraries │ │ ├── SafeMath.sol │ │ └── ElasticMath.sol │ ├── core │ │ ├── ElasticDAOFactory.sol │ │ └── ElasticDAO.sol │ └── tokens │ │ └── ElasticGovernanceToken.sol │ └── README.md ├── JUDGING_CRITERIA.md └── README.md /reports/readme.md: -------------------------------------------------------------------------------- 1 | reports 2 | -------------------------------------------------------------------------------- /images/c4-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/code-contests/HEAD/images/c4-logo.png -------------------------------------------------------------------------------- /SUBMISSION_POLICY.md: -------------------------------------------------------------------------------- 1 | # :red_circle: C4 Submission Policy 2 | 3 | See https://code423n4.com/submissions/ 4 | -------------------------------------------------------------------------------- /contests/README.md: -------------------------------------------------------------------------------- 1 | # :shield: Code Contests 2 | 3 | Current contests are found at https://code423n4.com 4 | -------------------------------------------------------------------------------- /JUDGING_CRITERIA.md: -------------------------------------------------------------------------------- 1 | # C4 Code Contest Judging Criteria 2 | 3 | See https://code423n4.com/judging-criteria/ 4 | -------------------------------------------------------------------------------- /reports/202102 - Slingshot.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/code-contests/HEAD/reports/202102 - Slingshot.pdf -------------------------------------------------------------------------------- /reports/202103 - ElasticDAO.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/code-contests/HEAD/reports/202103 - ElasticDAO.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Code 423n4 :wolf: 2 | ![banner](images/c4-logo.png) 3 | # :trophy: Code Contests 4 | 5 | This repo is soon to be deprecated. 6 | 7 | All up-to-date info is on the [C4 website](https://code423n4.com). Contests are announced via [Discord](https://discord.gg/EY5dvm3evD) and on [Twitter](https://twitter.com/code423n4). 8 | -------------------------------------------------------------------------------- /contests/01-slingshot/contracts/lib/LibERC20Token.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: None 2 | pragma solidity >=0.7.5; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; 6 | 7 | /// @title LibERC20Token 8 | /// @notice Utility function for ERC20 tokens 9 | library LibERC20Token { 10 | using SafeERC20 for IERC20; 11 | 12 | /// @param token Token to approve 13 | /// @param spender Address of wallet to approve spending for 14 | /// @param amount Amount of token to approve 15 | function approveIfBelow(IERC20 token, address spender, uint256 amount) internal { 16 | if (token.allowance(address(this), spender) < amount) { 17 | token.safeApprove(spender, uint256(0)); 18 | token.safeApprove(spender, uint256(-1)); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /contests/02-elasticdao/contracts/services/ReentryProtection.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPLv3 2 | pragma solidity 0.7.2; 3 | 4 | /// @author ElasticDAO - https://ElasticDAO.org 5 | /// @notice This contract is used for reentry protection 6 | /// based on implementation 7 | /// https://github.com/o0ragman0o/ReentryProtected/blob/master/ReentryProtected.sol 8 | /// @dev ElasticDAO network contracts can read/write from this contract 9 | contract ReentryProtection { 10 | // The reentry protection state mutex 11 | bool internal mutex = false; 12 | 13 | // This modifier can be used on functions with external calls to 14 | // prevent reentry attacks. 15 | // Constraints: 16 | // Protected functions must have only one point of exit. 17 | // Protected functions cannot use the `return` keyword 18 | // Protected functions return values must be through return parameters. 19 | modifier preventReentry() { 20 | require(!mutex, 'ElasticDAO: Reentry Detected'); 21 | 22 | mutex = true; 23 | _; 24 | mutex = false; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /contests/01-slingshot/contracts/lib/Strings.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: None 2 | pragma solidity >=0.7.5; 3 | 4 | /// @title Strings 5 | /// @notice Utility library for strings 6 | contract Strings { 7 | 8 | /// @notice Concat two strings 9 | /// @param str1 String to concat 10 | /// @param str2 String to concat 11 | /// @return result Concated strings 12 | function appendString(string memory str1, string memory str2) public pure returns (string memory result) { 13 | return string(abi.encodePacked(str1,str2)); 14 | } 15 | 16 | /// @notice Concat string and number 17 | /// @param str String to concat 18 | /// @param i Number to concat 19 | /// @return result Concated string and number 20 | function appendNumber(string memory str, uint i) public pure returns (string memory result) { 21 | return string(abi.encodePacked(str,i+uint(48))); 22 | } 23 | 24 | /// @notice Concat number and string 25 | /// @param i Number to concat 26 | /// @param str String to concat 27 | /// @return result Concated string and number 28 | function prependNumber(uint i, string memory str) public pure returns (string memory result) { 29 | return string(abi.encodePacked(i+uint(48),str)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /contests/01-slingshot/README.md: -------------------------------------------------------------------------------- 1 | # Code Contest 1: Slingshot Trading Contracts 2 | ## Code Contest Details 3 | **Pot Size** 4 | $20,000 5 | 6 | **Start Time** 7 | Feb 17, 1400 UTC 8 | 9 | **Stop Time** 10 | Feb 21, 2359 UTC 11 | 12 | **Judge(s)** 13 | Zak Cole 14 | [GitHub](https://github.com/zscole) 15 | 16 | ## About The Code 17 | As a system, Slingshot aggregates prices for available token pairs 18 | from a variety of decentralized exchanges and automated market making 19 | protocols. 20 | 21 | ### How It Works 22 | `Slingshot.sol` defines the general logic by which a transaction is 23 | handled and executed. 24 | 25 | The specific logic for each DEX/AMM is defined within its own corresponding module that is stored in the module registry. 26 | 27 | `Slingshot.sol` references these modules to appropriately execute a trade. 28 | `Slingshot.sol` also performs some safety checks to account for slippage 29 | and security. `Slingshot.sol` expect parameters to be passed from the Slingshot backend that provide the details related to how a given transaction should be executed. 30 | 31 | As this code conforms to [NatSpec](https://docs.soliditylang.org/en/v0.5.10/natspec-format.html#natspec-format) formatting specifications, lower level details regarding function can be found as comments within the code itself. 32 | 33 | ### Sharing Vulnerability/Discovery POC 34 | Should you identify a vulnerability or bug and have a POC that demonstrates the exploit, please create a private repository and share the POC with the contest Judge. When you have shared access, please also notify the judge on Discord to let them know you have shared it. -------------------------------------------------------------------------------- /contests/02-elasticdao/contracts/interfaces/IElasticToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPLv3 2 | pragma solidity 0.7.2; 3 | 4 | import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; 5 | 6 | interface IElasticToken is IERC20 { 7 | /** 8 | * @dev Returns the amount of shares owned by @param _account. 9 | * @param _account - address of the account 10 | * @return lambda uint256 - lambda is the number of shares 11 | */ 12 | function balanceOfInShares(address _account) external view returns (uint256 lambda); 13 | 14 | /** 15 | * @dev Reduces the balance(tokens) of @param _account by @param _amount 16 | * @param _account address of the account 17 | * @param _amount - the amount by which the number of tokens has to be reduced 18 | * @return bool 19 | */ 20 | function burn(address _account, uint256 _amount) external returns (bool); 21 | 22 | /** 23 | * @dev Reduces the balance(shares) of @param _account by @param _amount 24 | * @param _account - address of the account 25 | * @param _amount - the amount by which the number of shares has to be reduced 26 | * @return bool 27 | */ 28 | function burnShares(address _account, uint256 _amount) external returns (bool); 29 | 30 | /** 31 | * @dev mints @param _amount of shares for @param _account 32 | * @param _account address of the account 33 | * @param _amount - the amount of shares to be minted 34 | * @return bool 35 | */ 36 | function mintShares(address _account, uint256 _amount) external returns (bool); 37 | 38 | /** 39 | * @dev returns total number of token holders 40 | * @return uint256 41 | */ 42 | function numberOfTokenHolders() external view returns (uint256); 43 | 44 | /** 45 | * @dev Returns the total supply of shares in the DAO 46 | * @return lambda uint256 - lambda is the number of shares 47 | */ 48 | function totalSupplyInShares() external view returns (uint256 lambda); 49 | } 50 | -------------------------------------------------------------------------------- /contests/01-slingshot/contracts/Adminable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: None 2 | pragma solidity >=0.7.5; 3 | pragma abicoder v2; 4 | 5 | import "@openzeppelin/contracts/access/AccessControl.sol"; 6 | import "./Initializable.sol"; 7 | 8 | /// @title Admin Role Contract 9 | /// @author DEXAG, Inc. 10 | /// @notice This contract is a utility for an admin role access. 11 | abstract contract Adminable is Initializable, AccessControl { 12 | bytes32 public constant SLINGSHOT_ADMIN_ROLE = keccak256("SLINGSHOT_ADMIN_ROLE"); 13 | 14 | modifier onlyAdmin() { 15 | require(hasRole(SLINGSHOT_ADMIN_ROLE, _msgSender()), "Adminable: not an admin"); 16 | _; 17 | } 18 | 19 | /// @param role role hash 20 | modifier onlyRole(bytes32 role) { 21 | require(hasRole(role, _msgSender()), "Adminable: not a role"); 22 | _; 23 | } 24 | 25 | modifier onlyAdminIfInitialized() { 26 | if (isInitialized()) { 27 | // if admin is set, require admin to be msg.sender 28 | if(getRoleMemberCount(SLINGSHOT_ADMIN_ROLE) > 0) { 29 | require(hasRole(SLINGSHOT_ADMIN_ROLE, _msgSender()), "Adminable: not an admin"); 30 | _; 31 | } 32 | // if no admin is set, just silently skip the execution. Proxy owner 33 | // and slingshot admin may be two separate wallets so we don't revert 34 | // the transaction to avoid jeopardizing contract implementation deployemnt 35 | // - at these gas prices, it'd be almost a crime if we did. 36 | } else { 37 | // if not initialized, just let it execute 38 | _; 39 | } 40 | } 41 | 42 | /// @notice Set default admin role 43 | /// @param _admin Address to control admin functions 44 | function initializeAdmin(address _admin) internal { 45 | _setRoleAdmin(SLINGSHOT_ADMIN_ROLE, SLINGSHOT_ADMIN_ROLE); 46 | _setupRole(SLINGSHOT_ADMIN_ROLE, _admin); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /contests/01-slingshot/contracts/module/CurveModule.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: None 2 | pragma solidity >=0.7.5; 3 | pragma abicoder v2; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | import "@openzeppelin/contracts/math/SafeMath.sol"; 7 | import "../lib/LibERC20Token.sol"; 8 | 9 | interface ICurvePool { 10 | function exchange( 11 | int128 i, 12 | int128 j, 13 | uint256 dx, 14 | uint256 min_dy 15 | ) external; 16 | 17 | function exchange_underlying( 18 | int128 i, 19 | int128 j, 20 | uint256 dx, 21 | uint256 min_dy 22 | ) external; 23 | } 24 | 25 | /// @title Slingshot Curve Module 26 | /// @dev In addition to tradeAll, the only unique logic in this contract calculates post-trade 27 | /// balance because Curve's innovative design choice of not returning an output amount. 28 | contract CurveModule { 29 | using SafeMath for uint256; 30 | using LibERC20Token for IERC20; 31 | 32 | /// @notice 33 | /// @dev 34 | /// @param curvePool Pool address to trade on 35 | /// @param iToken Address of token sold 36 | /// @param jToken Address of token bought 37 | /// @param i Index array of the token sold 38 | /// @param j Index array of the token bought 39 | /// @param dx Amount of the token to sell 40 | /// @param tradeAll If true, it overrides totalAmountIn with current token balance 41 | /// @return 42 | function swap( 43 | address curvePool, 44 | address iToken, 45 | address jToken, 46 | int128 i, 47 | int128 j, 48 | uint256 dx, 49 | bool tradeAll, 50 | bool underlyingTokens 51 | ) public payable returns (uint256) { 52 | if (tradeAll) dx = IERC20(iToken).balanceOf(address(this)); 53 | IERC20(iToken).approveIfBelow(curvePool, dx); 54 | // Calculate post-trade balance 55 | uint256 beforeBalance = IERC20(jToken).balanceOf(address(this)); 56 | 57 | underlyingTokens 58 | ? ICurvePool(curvePool).exchange_underlying(i, j, dx, uint(1)) 59 | : ICurvePool(curvePool).exchange(i, j, dx, uint(1)); 60 | 61 | return IERC20(jToken).balanceOf(address(this)).sub(beforeBalance); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /contests/01-slingshot/contracts/module/BalancerModule.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: None 2 | pragma solidity >=0.7.5; 3 | pragma abicoder v2; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | import "@openzeppelin/contracts/math/SafeMath.sol"; 7 | import "../lib/LibERC20Token.sol"; 8 | 9 | interface IBalancerPool { 10 | /// @dev Sell `tokenAmountIn` of `tokenIn` and receive `tokenOut`. 11 | /// @param tokenIn The token being sold 12 | /// @param tokenAmountIn The amount of `tokenIn` to sell. 13 | /// @param tokenOut The token being bought. 14 | /// @param minAmountOut The minimum amount of `tokenOut` to buy. 15 | /// @param maxPrice The maximum value for `spotPriceAfter`. 16 | /// @return tokenAmountOut The amount of `tokenOut` bought. 17 | /// @return spotPriceAfter The new marginal spot price of the given 18 | /// token pair for this pool. 19 | function swapExactAmountIn( 20 | address tokenIn, 21 | uint tokenAmountIn, 22 | address tokenOut, 23 | uint minAmountOut, 24 | uint maxPrice 25 | ) external returns (uint tokenAmountOut, uint spotPriceAfter); 26 | } 27 | 28 | /// @title Slingshot Balancer Module 29 | contract BalancerModule { 30 | using SafeMath for uint256; 31 | using LibERC20Token for IERC20; 32 | 33 | /// @param pool Balancer pool to trade on 34 | /// @param tokenIn Address of token sold 35 | /// @param tokenOut Address of token bought 36 | /// @param totalAmountIn Amount of token sold 37 | /// @param tradeAll If true, it overrides totalAmountIn with current token balance 38 | /// @return Amount of token bought 39 | function swap( 40 | address pool, 41 | address tokenIn, 42 | address tokenOut, 43 | uint totalAmountIn, 44 | bool tradeAll 45 | ) public payable returns (uint256) { 46 | if (tradeAll) totalAmountIn = IERC20(tokenIn).balanceOf(address(this)); 47 | 48 | IERC20(tokenIn).approveIfBelow(pool, totalAmountIn); 49 | 50 | (uint boughtAmount,) = IBalancerPool(pool).swapExactAmountIn( 51 | tokenIn, 52 | totalAmountIn, 53 | tokenOut, 54 | 1, // minAmountOut 55 | uint256(-1) // maxPrice 56 | ); 57 | return boughtAmount; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /contests/02-elasticdao/README.md: -------------------------------------------------------------------------------- 1 | # Code Contest 2: ElasticDAO Contracts 2 | ## Code Contest Details 3 | **Pot Size** 4 | 22 ETH TOTAL (~$35,000) 5 | 20 ETH for Vulerabilities 6 | 2 ETH for Gas Improvements (not related to the storage pattern) 7 | 8 | **Start Time** 9 | Feb 25, 1600 UTC 10 | 11 | **Stop Time** 12 | March 3, 2359 UTC 13 | 14 | **Sponsor** 15 | LSDan | ElasticDAO 16 | [GitHub](https://github.com/dmvt) 17 | 18 | **Judges** 19 | Zak Cole 20 | [GitHub](https://github.com/zscole) 21 | 22 | ## About The Code 23 | Elastic DAO is a governance protocol that attempts to balance the competing interests between the different participants in a decentralized ecosystem. Elastic DAO achieves this by reducing the overall influence that money and early adopters have in existing DAO governance models. 24 | 25 | ElasticDAO relies on Snapshot for it's voting approach (due to gas prices) pending implementation with a 26 | layer 2 solution after launch. For this reason, most functions which would be performed by voting are 27 | actually executed by a multisig. This multisig will be the owner of the proxy contracts, the ElasticDAO 28 | controller, the burner, and the minter. 29 | 30 | Code with tests and tooling is available at https://github.com/elasticdao/contracts/tree/c657b84469ba33efd8914c7e847830d82cb0f3ca 31 | Docs are available at https://docs.elasticdao.org 32 | 33 | ### How It Works 34 | `core/ElasticDAO.sol` defines the logic for deploying, initializing, summoning, joining, and exiting a DAO. 35 | `core/ElasticDAOFactory.sol` provides a singular approach for deploying DAOs and is meant to be managed 36 | by the first DAO, ElasticDAO. 37 | `tokens/ElasticGovernanceToken.sol` is a rebasing token that conforms to the ERC20 spec. 38 | 39 | Storage contracts follow a version of the Eternal Storage pattern and are found in `src/models`. 40 | 41 | As this code conforms to [NatSpec](https://docs.soliditylang.org/en/v0.7.2/natspec-format.html#natspec-format) formatting specifications, lower level details regarding function can be found as comments within the code itself. 42 | 43 | ### Sharing Vulnerability/Discovery POC 44 | Should you identify a vulnerability or bug and have a POC that demonstrates the exploit, please create a private repository and share the POC with the contest Judge. When you have shared access, please also notify the judge on Discord to let them know you have shared it. -------------------------------------------------------------------------------- /contests/02-elasticdao/contracts/models/TokenHolder.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPLv3 2 | pragma solidity 0.7.2; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import './Ecosystem.sol'; 6 | import './EternalModel.sol'; 7 | import './Token.sol'; 8 | import '../libraries/SafeMath.sol'; 9 | import '../services/ReentryProtection.sol'; 10 | 11 | /** 12 | * @title a data storage for Token holders 13 | * @author ElasticDAO - https://ElasticDAO.org 14 | * @notice This contract is used for storing token data 15 | * @dev ElasticDAO network contracts can read/write from this contract 16 | * Serialize - Translation of data from the concerned struct to key-value pairs 17 | * Deserialize - Translation of data from the key-value pairs to a struct 18 | */ 19 | contract TokenHolder is EternalModel, ReentryProtection { 20 | struct Instance { 21 | address account; 22 | uint256 lambda; 23 | Ecosystem.Instance ecosystem; 24 | Token.Instance token; 25 | } 26 | 27 | event Serialized(address indexed account, address indexed token); 28 | 29 | function deserialize( 30 | address _account, 31 | Ecosystem.Instance memory _ecosystem, 32 | Token.Instance memory _token 33 | ) external view returns (Instance memory record) { 34 | record.account = _account; 35 | record.ecosystem = _ecosystem; 36 | record.token = _token; 37 | 38 | if (_exists(_account, _token)) { 39 | record.lambda = getUint(keccak256(abi.encode(record.token.uuid, record.account, 'lambda'))); 40 | } 41 | 42 | return record; 43 | } 44 | 45 | function exists(address _account, Token.Instance memory _token) external view returns (bool) { 46 | return _exists(_account, _token); 47 | } 48 | 49 | /** 50 | * @dev serializes Instance struct 51 | * @param _record Instance 52 | */ 53 | function serialize(Instance memory _record) external preventReentry { 54 | require(msg.sender == _record.token.uuid, 'ElasticDAO: Unauthorized'); 55 | 56 | setUint(keccak256(abi.encode(_record.token.uuid, _record.account, 'lambda')), _record.lambda); 57 | setBool(keccak256(abi.encode(_record.token.uuid, _record.account, 'exists')), true); 58 | 59 | emit Serialized(_record.account, _record.token.uuid); 60 | } 61 | 62 | function _exists(address _account, Token.Instance memory _token) internal view returns (bool) { 63 | return getBool(keccak256(abi.encode(_token.uuid, _account, 'exists'))); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /contests/01-slingshot/contracts/ModuleRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: None 2 | pragma solidity >=0.7.5; 3 | 4 | import "./Adminable.sol"; 5 | 6 | /// @title Module Registry Contract 7 | /// @author DEXAG, Inc. 8 | /// @notice This contract provides the logic for querying, maintaining, and updating Slingshot modules. 9 | /// @dev When a new module is deployed, it must be registered. If the logic for a particular 10 | /// DEX/AMM changes, a new module must be deployed and registered. 11 | contract ModuleRegistry is Adminable { 12 | /// @notice This is an index which indicates the validity of a module 13 | mapping(address => bool) public modulesIndex; 14 | 15 | /// @notice Slingshot.sol address 16 | address public slingshot; 17 | 18 | event ModuleRegistered(address moduleAddress); 19 | event ModuleUnregistered(address moduleAddress); 20 | event NewSlingshot(address oldAddress, address newAddress); 21 | 22 | /// @notice Use this function for post upgrade setup 23 | /// @param _admin Address to control admin functions 24 | function postUpgrade(address _admin) external onlyAdminIfInitialized { 25 | // skip when initialized 26 | if (!isInitialized()) initialize(_admin); 27 | // all other post upgrade setup below 28 | } 29 | 30 | /// @notice Initialization function for proxy setup 31 | /// @param _admin Address to control admin functions 32 | function initialize(address _admin) internal initializer { 33 | initializeAdmin(_admin); 34 | } 35 | 36 | /// @notice Checks if given address is a module 37 | /// @param _moduleAddress Address of the module in question 38 | /// @return true if address is module 39 | function isModule(address _moduleAddress) external view returns (bool) { 40 | return modulesIndex[_moduleAddress]; 41 | } 42 | 43 | /// @param _moduleAddress Address of the module to register 44 | function registerSwapModule(address _moduleAddress) external onlyAdmin { 45 | modulesIndex[_moduleAddress] = true; 46 | emit ModuleRegistered(_moduleAddress); 47 | } 48 | 49 | /// @param _moduleAddress Address of the module to unregister 50 | function unregisterSwapModule(address _moduleAddress) external onlyAdmin { 51 | delete modulesIndex[_moduleAddress]; 52 | emit ModuleRegistered(_moduleAddress); 53 | } 54 | 55 | /// @param _slingshot Slingshot.sol address implementation 56 | function setSlingshot(address _slingshot) external onlyAdmin { 57 | address oldAddress = slingshot; 58 | slingshot = _slingshot; 59 | emit NewSlingshot(oldAddress, _slingshot); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /contests/01-slingshot/contracts/module/UniswapModule.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: None 2 | pragma solidity >=0.7.5; 3 | pragma abicoder v2; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | import "@openzeppelin/contracts/math/SafeMath.sol"; 7 | import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol"; 8 | import "../lib/LibERC20Token.sol"; 9 | 10 | /// @title Slingshot Uniswap Module 11 | /// @dev tradeAll is the only unique logic in this module. If true, the remaining 12 | /// portion of a trade is filled in this hop. This addresses the issue of dust 13 | /// to account for slippage and ensure that the user can only receive more of an 14 | /// asset than expected. 15 | contract UniswapModule { 16 | using SafeMath for uint256; 17 | using LibERC20Token for IERC20; 18 | 19 | address public constant uniswapRouter = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D; 20 | 21 | /// @param amount Amount of the token being exchanged 22 | /// @param path Array of token addresses to swap 23 | /// @param amountOutMin Minimum accepted amount of token bought 24 | /// @param amountInMax Maximum accpeted amount of token sold 25 | /// @param swapExactTokensForTokens Indicates which side of trade should be a fixed amount 26 | /// @param tradeAll If true, it overrides totalAmountIn with current token balance 27 | /// @return 28 | function swap( 29 | uint amount, 30 | address[] memory path, 31 | uint amountOutMin, 32 | uint amountInMax, 33 | bool swapExactTokensForTokens, 34 | bool tradeAll 35 | ) public payable returns (uint256){ 36 | require(path.length > 0, "UniswapModule: path length must be >0"); 37 | 38 | if (tradeAll) amount = IERC20(path[0]).balanceOf(address(this)); 39 | IERC20(path[0]).approveIfBelow(uniswapRouter, amount); 40 | 41 | if (swapExactTokensForTokens) { 42 | amountOutMin = amountOutMin == 0 ? 1 : amountOutMin; 43 | uint256[] memory amounts = 44 | IUniswapV2Router02(uniswapRouter).swapExactTokensForTokens( 45 | amount, 46 | amountOutMin, 47 | path, 48 | address(this), 49 | block.timestamp 50 | ); 51 | return amounts[amounts.length - 1]; 52 | } else { 53 | amountInMax = amountInMax == 0 ? uint(-1) : amountInMax; 54 | uint256[] memory amounts = 55 | IUniswapV2Router02(uniswapRouter).swapTokensForExactTokens( 56 | amount, 57 | amountInMax, 58 | path, 59 | address(this), 60 | block.timestamp 61 | ); 62 | return amounts[amounts.length - 1]; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /contests/01-slingshot/contracts/Initializable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // solhint-disable-next-line compiler-version 4 | pragma solidity >=0.7.5; 5 | 6 | 7 | /** 8 | * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed 9 | * behind a proxy. Since a proxied contract can't have a constructor, it's common to move constructor logic to an 10 | * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer 11 | * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. 12 | * 13 | * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as 14 | * possible by providing the encoded function call as the `_data` argument to {UpgradeableProxy-constructor}. 15 | * 16 | * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure 17 | * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. 18 | */ 19 | abstract contract Initializable { 20 | 21 | /** 22 | * @dev Indicates that the contract has been initialized. 23 | */ 24 | bool private _initialized; 25 | 26 | /** 27 | * @dev Indicates that the contract is in the process of being initialized. 28 | */ 29 | bool private _initializing; 30 | 31 | /** 32 | * @dev Modifier to protect an initializer function from being invoked twice. 33 | */ 34 | modifier initializer() { 35 | require(_initializing || _isConstructor() || !_initialized, "Initializable: contract is already initialized"); 36 | 37 | bool isTopLevelCall = !_initializing; 38 | if (isTopLevelCall) { 39 | _initializing = true; 40 | _initialized = true; 41 | } 42 | 43 | _; 44 | 45 | if (isTopLevelCall) { 46 | _initializing = false; 47 | } 48 | } 49 | 50 | function isInitialized() public view returns (bool) { 51 | return _initialized; 52 | } 53 | 54 | /// @dev Returns true if and only if the function is running in the constructor 55 | function _isConstructor() private view returns (bool) { 56 | // extcodesize checks the size of the code stored in an address, and 57 | // address returns the current address. Since the code is still not 58 | // deployed when running a constructor, any checks on its code size will 59 | // yield zero, making it an effective way to detect if a contract is 60 | // under construction or not. 61 | address self = address(this); 62 | uint256 cs; 63 | // solhint-disable-next-line no-inline-assembly 64 | assembly { cs := extcodesize(self) } 65 | return cs == 0; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /contests/01-slingshot/contracts/module/SushiSwapModule.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: None 2 | pragma solidity >=0.7.5; 3 | pragma abicoder v2; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | import "@openzeppelin/contracts/math/SafeMath.sol"; 7 | import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol"; 8 | import "../lib/LibERC20Token.sol"; 9 | import "hardhat/console.sol"; 10 | 11 | /// @title Slingshot SushiSwap Module 12 | /// @dev tradeAll is the only unique logic in this module. If true, the remaining 13 | /// portion of a trade is filled in this hop. This addresses the issue of dust 14 | /// to account for slippage and ensure that the user can only receive more of an 15 | /// asset than expected. 16 | contract SushiSwapModule { 17 | using SafeMath for uint256; 18 | using LibERC20Token for IERC20; 19 | 20 | address public constant sushiSwapRouter = 0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F; 21 | 22 | /// @param amount Amount of the token being exchanged 23 | /// @param path Array of token addresses to swap 24 | /// @param amountOutMin Minimum accepted amount of token bought 25 | /// @param amountInMax Maximum accpeted amount of token sold 26 | /// @param swapExactTokensForTokens Indicates which side of trade should be a fixed amount 27 | /// @param tradeAll If true, it overrides totalAmountIn with current token balance 28 | /// @return 29 | function swap( 30 | uint amount, 31 | address[] memory path, 32 | uint amountOutMin, 33 | uint amountInMax, 34 | bool swapExactTokensForTokens, 35 | bool tradeAll 36 | ) public payable returns (uint256){ 37 | require(path.length > 0, "SushiSwapModule: path length must be >0"); 38 | 39 | if (tradeAll) amount = IERC20(path[0]).balanceOf(address(this)); 40 | IERC20(path[0]).approveIfBelow(sushiSwapRouter, amount); 41 | 42 | if (swapExactTokensForTokens) { 43 | amountOutMin = amountOutMin == 0 ? 1 : amountOutMin; 44 | uint256[] memory amounts = 45 | IUniswapV2Router02(sushiSwapRouter).swapExactTokensForTokens( 46 | amount, 47 | amountOutMin, 48 | path, 49 | address(this), 50 | block.timestamp 51 | ); 52 | return amounts[amounts.length - 1]; 53 | } else { 54 | amountInMax = amountInMax == 0 ? uint(-1) : amountInMax; 55 | uint256[] memory amounts = 56 | IUniswapV2Router02(sushiSwapRouter).swapTokensForExactTokens( 57 | amount, 58 | amountInMax, 59 | path, 60 | address(this), 61 | block.timestamp 62 | ); 63 | return amounts[amounts.length - 1]; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /contests/02-elasticdao/contracts/libraries/SafeMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.7.2; 3 | 4 | /** 5 | * @dev Wrappers over Solidity's arithmetic operations with added overflow 6 | * checks. 7 | * 8 | * Arithmetic operations in Solidity wrap on overflow. This can easily result 9 | * in bugs, because programmers usually assume that an overflow raises an 10 | * error, which is the standard behavior in high level programming languages. 11 | * `SafeMath` restores this intuition by reverting the transaction when an 12 | * operation overflows. 13 | * 14 | * Using this library instead of the unchecked operations eliminates an entire 15 | * class of bugs, so it's recommended to use it always. 16 | */ 17 | library SafeMath { 18 | /** 19 | * @dev Returns the addition of two unsigned integers, reverting on 20 | * overflow. 21 | * 22 | * Counterpart to Solidity's `+` operator. 23 | * 24 | * Requirements: 25 | * 26 | * - Addition cannot overflow. 27 | */ 28 | function add(uint256 a, uint256 b) internal pure returns (uint256) { 29 | uint256 c = a + b; 30 | require(c >= a, 'SafeMath: addition overflow'); 31 | 32 | return c; 33 | } 34 | 35 | /** 36 | * @dev Returns the subtraction of two unsigned integers, reverting on 37 | * overflow (when the result is negative). 38 | * 39 | * Counterpart to Solidity's `-` operator. 40 | * 41 | * Requirements: 42 | * 43 | * - Subtraction cannot overflow. 44 | */ 45 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 46 | require(b <= a, 'SafeMath: subtraction overflow'); 47 | uint256 c = a - b; 48 | return c; 49 | } 50 | 51 | /** 52 | * @dev Returns the multiplication of two unsigned integers, reverting on 53 | * overflow. 54 | * 55 | * Counterpart to Solidity's `*` operator. 56 | * 57 | * Requirements: 58 | * 59 | * - Multiplication cannot overflow. 60 | */ 61 | function mul(uint256 a, uint256 b) internal pure returns (uint256) { 62 | // Gas optimization: this is cheaper than requiring 'a' not being zero, but the 63 | // benefit is lost if 'b' is also tested. 64 | // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 65 | if (a == 0) { 66 | return 0; 67 | } 68 | 69 | uint256 c = a * b; 70 | require(c / a == b, 'SafeMath: multiplication overflow'); 71 | 72 | return c; 73 | } 74 | 75 | /** 76 | * @dev Returns one unsigned integer to the power of another, reverting on 77 | * a multiplication overflow. 78 | * 79 | * Counterpart to Solidity's `**` operator. 80 | */ 81 | function pow(uint256 base, uint256 exponent) internal pure returns (uint256) { 82 | if (exponent == 0) { 83 | return 1; 84 | } else if (exponent == 1) { 85 | return base; 86 | } else if (base == 0 && exponent != 0) { 87 | return 0; 88 | } else { 89 | uint256 z = base; 90 | for (uint256 i = 1; i < exponent; i += 1) z = mul(z, base); 91 | return z; 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /contests/02-elasticdao/contracts/models/EternalModel.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPLv3 2 | pragma solidity 0.7.2; 3 | pragma experimental ABIEncoderV2; 4 | 5 | /** 6 | * @title Implementation of Eternal Storage for ElasticDAO - 7 | * - (https://fravoll.github.io/solidity-patterns/eternal_storage.html) 8 | * @author ElasticDAO - https://ElasticDAO.org 9 | * @notice This contract is used for storing contract network data 10 | * @dev ElasticDAO network contracts can read/write from this contract 11 | */ 12 | contract EternalModel { 13 | struct Storage { 14 | mapping(bytes32 => address) addressStorage; 15 | mapping(bytes32 => bool) boolStorage; 16 | mapping(bytes32 => bytes) bytesStorage; 17 | mapping(bytes32 => int256) intStorage; 18 | mapping(bytes32 => string) stringStorage; 19 | mapping(bytes32 => uint256) uIntStorage; 20 | } 21 | 22 | Storage internal s; 23 | 24 | /** 25 | * @notice Getter Functions 26 | */ 27 | 28 | /** 29 | * @notice Gets stored contract data in unit256 format 30 | * @param _key bytes32 location should be keccak256 and abi.encodePacked 31 | * @return uint256 _value from storage _key location 32 | */ 33 | function getUint(bytes32 _key) internal view returns (uint256) { 34 | return s.uIntStorage[_key]; 35 | } 36 | 37 | /** 38 | * @notice Get stored contract data in string format 39 | * @param _key bytes32 location should be keccak256 and abi.encodePacked 40 | * @return string _value from storage _key location 41 | */ 42 | function getString(bytes32 _key) internal view returns (string memory) { 43 | return s.stringStorage[_key]; 44 | } 45 | 46 | /** 47 | * @notice Get stored contract data in address format 48 | * @param _key bytes32 location should be keccak256 and abi.encodePacked 49 | * @return address _value from storage _key location 50 | */ 51 | function getAddress(bytes32 _key) internal view returns (address) { 52 | return s.addressStorage[_key]; 53 | } 54 | 55 | /** 56 | * @notice Get stored contract data in bool format 57 | * @param _key bytes32 location should be keccak256 and abi.encodePacked 58 | * @return bool _value from storage _key location 59 | */ 60 | function getBool(bytes32 _key) internal view returns (bool) { 61 | return s.boolStorage[_key]; 62 | } 63 | 64 | /** 65 | * @notice Setters Functions 66 | */ 67 | 68 | /** 69 | * @notice Store contract data in uint256 format 70 | * @dev restricted to latest ElasticDAO Networks contracts 71 | * @param _key bytes32 location should be keccak256 and abi.encodePacked 72 | * @param _value uint256 value 73 | */ 74 | function setUint(bytes32 _key, uint256 _value) internal { 75 | s.uIntStorage[_key] = _value; 76 | } 77 | 78 | /** 79 | * @notice Store contract data in string format 80 | * @dev restricted to latest ElasticDAO Networks contracts 81 | * @param _key bytes32 location should be keccak256 and abi.encodePacked 82 | * @param _value string value 83 | */ 84 | function setString(bytes32 _key, string memory _value) internal { 85 | s.stringStorage[_key] = _value; 86 | } 87 | 88 | /** 89 | * @notice Store contract data in address format 90 | * @dev restricted to latest ElasticDAO Networks contracts 91 | * @param _key bytes32 location should be keccak256 and abi.encodePacked 92 | * @param _value address value 93 | */ 94 | function setAddress(bytes32 _key, address _value) internal { 95 | s.addressStorage[_key] = _value; 96 | } 97 | 98 | /** 99 | * @notice Store contract data in bool format 100 | * @dev restricted to latest ElasticDAO Networks contracts 101 | * @param _key bytes32 location should be keccak256 and abi.encodePacked 102 | * @param _value bool value 103 | */ 104 | function setBool(bytes32 _key, bool _value) internal { 105 | s.boolStorage[_key] = _value; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /contests/02-elasticdao/contracts/models/DAO.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPLv3 2 | pragma solidity 0.7.2; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import './Ecosystem.sol'; 6 | import './EternalModel.sol'; 7 | import '../libraries/SafeMath.sol'; 8 | import '../services/ReentryProtection.sol'; 9 | 10 | /** 11 | * @author ElasticDAO - https://ElasticDAO.org 12 | * @notice This contract is used for storing core DAO data 13 | * @dev ElasticDAO network contracts can read/write from this contract 14 | */ 15 | contract DAO is EternalModel, ReentryProtection { 16 | struct Instance { 17 | address uuid; 18 | address[] summoners; 19 | bool summoned; 20 | string name; 21 | uint256 maxVotingLambda; 22 | uint256 numberOfSummoners; 23 | Ecosystem.Instance ecosystem; 24 | } 25 | 26 | event Serialized(address indexed uuid); 27 | 28 | /** 29 | * @dev deserializes Instance struct 30 | * @param _uuid - address of the unique user ID 31 | * @return record Instance 32 | */ 33 | function deserialize(address _uuid, Ecosystem.Instance memory _ecosystem) 34 | external 35 | view 36 | returns (Instance memory record) 37 | { 38 | record.uuid = _uuid; 39 | record.ecosystem = _ecosystem; 40 | 41 | if (_exists(_uuid)) { 42 | record.maxVotingLambda = getUint(keccak256(abi.encode(_uuid, 'maxVotingLambda'))); 43 | record.name = getString(keccak256(abi.encode(_uuid, 'name'))); 44 | record.numberOfSummoners = getUint(keccak256(abi.encode(_uuid, 'numberOfSummoners'))); 45 | record.summoned = getBool(keccak256(abi.encode(_uuid, 'summoned'))); 46 | } 47 | 48 | return record; 49 | } 50 | 51 | /** 52 | * @dev checks if @param _uuid exists 53 | * @param _uuid - address of the unique user ID 54 | * @return recordExists bool 55 | */ 56 | function exists(address _uuid, Ecosystem.Instance memory) external view returns (bool) { 57 | return _exists(_uuid); 58 | } 59 | 60 | function getSummoner(Instance memory _dao, uint256 _index) external view returns (address) { 61 | return getAddress(keccak256(abi.encode(_dao.uuid, 'summoners', _index))); 62 | } 63 | 64 | /** 65 | * @dev checks if @param _uuid where _uuid is msg.sender - is a Summoner 66 | * @param _dao DAO.Instance 67 | * @param _summonerAddress address 68 | * @return bool 69 | */ 70 | function isSummoner(Instance memory _dao, address _summonerAddress) external view returns (bool) { 71 | return getBool(keccak256(abi.encode(_dao.uuid, 'summoner', _summonerAddress))); 72 | } 73 | 74 | /** 75 | * @dev serializes Instance struct 76 | * @param _record Instance 77 | */ 78 | function serialize(Instance memory _record) external preventReentry { 79 | require( 80 | msg.sender == _record.uuid || msg.sender == _record.ecosystem.configuratorAddress, 81 | 'ElasticDAO: Unauthorized' 82 | ); 83 | 84 | setUint(keccak256(abi.encode(_record.uuid, 'maxVotingLambda')), _record.maxVotingLambda); 85 | setString(keccak256(abi.encode(_record.uuid, 'name')), _record.name); 86 | setBool(keccak256(abi.encode(_record.uuid, 'summoned')), _record.summoned); 87 | 88 | if (_record.summoners.length > 0) { 89 | _record.numberOfSummoners = _record.summoners.length; 90 | setUint(keccak256(abi.encode(_record.uuid, 'numberOfSummoners')), _record.numberOfSummoners); 91 | for (uint256 i = 0; i < _record.numberOfSummoners; i += 1) { 92 | setBool(keccak256(abi.encode(_record.uuid, 'summoner', _record.summoners[i])), true); 93 | setAddress(keccak256(abi.encode(_record.uuid, 'summoners', i)), _record.summoners[i]); 94 | } 95 | } 96 | 97 | setBool(keccak256(abi.encode(_record.uuid, 'exists')), true); 98 | 99 | emit Serialized(_record.uuid); 100 | } 101 | 102 | function _exists(address _uuid) internal view returns (bool) { 103 | return getBool(keccak256(abi.encode(_uuid, 'exists'))); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /contests/02-elasticdao/contracts/models/Ecosystem.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPLv3 2 | pragma solidity 0.7.2; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import './EternalModel.sol'; 6 | import '../libraries/SafeMath.sol'; 7 | import '../services/ReentryProtection.sol'; 8 | 9 | /** 10 | * @title ElasticDAO ecosystem 11 | * @author ElasticDAO - https://ElasticDAO.org 12 | * @notice This contract is used for storing core dao data 13 | * @dev ElasticDAO network contracts can read/write from this contract 14 | * @dev Serialize - Translation of data from the concerned struct to key-value pairs 15 | * @dev Deserialize - Translation of data from the key-value pairs to a struct 16 | */ 17 | contract Ecosystem is EternalModel, ReentryProtection { 18 | struct Instance { 19 | address daoAddress; 20 | // Models 21 | address daoModelAddress; 22 | address ecosystemModelAddress; 23 | address tokenHolderModelAddress; 24 | address tokenModelAddress; 25 | // Services 26 | address configuratorAddress; 27 | // Tokens 28 | address governanceTokenAddress; 29 | } 30 | 31 | event Serialized(address indexed _daoAddress); 32 | 33 | /** 34 | * @dev deserializes Instance struct 35 | * @param _daoAddress - address of the unique user ID 36 | * @return record Instance 37 | */ 38 | function deserialize(address _daoAddress) external view returns (Instance memory record) { 39 | if (_exists(_daoAddress)) { 40 | record.daoAddress = _daoAddress; 41 | record.configuratorAddress = getAddress( 42 | keccak256(abi.encode(record.daoAddress, 'configuratorAddress')) 43 | ); 44 | record.daoModelAddress = getAddress( 45 | keccak256(abi.encode(record.daoAddress, 'daoModelAddress')) 46 | ); 47 | record.ecosystemModelAddress = address(this); 48 | record.governanceTokenAddress = getAddress( 49 | keccak256(abi.encode(record.daoAddress, 'governanceTokenAddress')) 50 | ); 51 | record.tokenHolderModelAddress = getAddress( 52 | keccak256(abi.encode(record.daoAddress, 'tokenHolderModelAddress')) 53 | ); 54 | record.tokenModelAddress = getAddress( 55 | keccak256(abi.encode(record.daoAddress, 'tokenModelAddress')) 56 | ); 57 | } 58 | 59 | return record; 60 | } 61 | 62 | /** 63 | * @dev checks if @param _daoAddress 64 | * @param _daoAddress - address of the unique user ID 65 | * @return recordExists bool 66 | */ 67 | function exists(address _daoAddress) external view returns (bool recordExists) { 68 | return _exists(_daoAddress); 69 | } 70 | 71 | /** 72 | * @dev serializes Instance struct 73 | * @param _record Instance 74 | */ 75 | function serialize(Instance memory _record) external preventReentry { 76 | bool recordExists = _exists(_record.daoAddress); 77 | 78 | require( 79 | msg.sender == _record.daoAddress || 80 | msg.sender == _record.configuratorAddress || 81 | (_record.daoAddress == address(0) && !recordExists), 82 | 'ElasticDAO: Unauthorized' 83 | ); 84 | 85 | setAddress( 86 | keccak256(abi.encode(_record.daoAddress, 'configuratorAddress')), 87 | _record.configuratorAddress 88 | ); 89 | setAddress( 90 | keccak256(abi.encode(_record.daoAddress, 'daoModelAddress')), 91 | _record.daoModelAddress 92 | ); 93 | setAddress( 94 | keccak256(abi.encode(_record.daoAddress, 'governanceTokenAddress')), 95 | _record.governanceTokenAddress 96 | ); 97 | setAddress( 98 | keccak256(abi.encode(_record.daoAddress, 'tokenHolderModelAddress')), 99 | _record.tokenHolderModelAddress 100 | ); 101 | setAddress( 102 | keccak256(abi.encode(_record.daoAddress, 'tokenModelAddress')), 103 | _record.tokenModelAddress 104 | ); 105 | 106 | setBool(keccak256(abi.encode(_record.daoAddress, 'exists')), true); 107 | 108 | emit Serialized(_record.daoAddress); 109 | } 110 | 111 | function _exists(address _daoAddress) internal view returns (bool recordExists) { 112 | return getBool(keccak256(abi.encode(_daoAddress, 'exists'))); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /contests/02-elasticdao/contracts/models/Token.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPLv3 2 | pragma solidity 0.7.2; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import './Ecosystem.sol'; 6 | import './EternalModel.sol'; 7 | import '../libraries/SafeMath.sol'; 8 | import '../services/ReentryProtection.sol'; 9 | import '../tokens/ElasticGovernanceToken.sol'; 10 | 11 | /** 12 | * @title A data storage for EGT (Elastic Governance Token) 13 | * @notice More info about EGT could be found in ./tokens/ElasticGovernanceToken.sol 14 | * @notice This contract is used for storing token data 15 | * @dev ElasticDAO network contracts can read/write from this contract 16 | * Serialize - Translation of data from the concerned struct to key-value pairs 17 | * Deserialize - Translation of data from the key-value pairs to a struct 18 | */ 19 | contract Token is EternalModel, ReentryProtection { 20 | struct Instance { 21 | address uuid; 22 | string name; 23 | string symbol; 24 | uint256 eByL; 25 | uint256 elasticity; 26 | uint256 k; 27 | uint256 lambda; 28 | uint256 m; 29 | uint256 maxLambdaPurchase; 30 | uint256 numberOfTokenHolders; 31 | Ecosystem.Instance ecosystem; 32 | } 33 | 34 | event Serialized(address indexed uuid); 35 | 36 | /** 37 | * @dev deserializes Instance struct 38 | * @param _uuid - address of the unique user ID 39 | * @return record Instance 40 | */ 41 | function deserialize(address _uuid, Ecosystem.Instance memory _ecosystem) 42 | external 43 | view 44 | returns (Instance memory record) 45 | { 46 | record.uuid = _uuid; 47 | record.ecosystem = _ecosystem; 48 | 49 | if (_exists(_uuid)) { 50 | record.eByL = getUint(keccak256(abi.encode(_uuid, 'eByL'))); 51 | record.elasticity = getUint(keccak256(abi.encode(_uuid, 'elasticity'))); 52 | record.k = getUint(keccak256(abi.encode(_uuid, 'k'))); 53 | record.lambda = getUint(keccak256(abi.encode(_uuid, 'lambda'))); 54 | record.m = getUint(keccak256(abi.encode(_uuid, 'm'))); 55 | record.maxLambdaPurchase = getUint(keccak256(abi.encode(_uuid, 'maxLambdaPurchase'))); 56 | record.name = getString(keccak256(abi.encode(_uuid, 'name'))); 57 | record.numberOfTokenHolders = getUint(keccak256(abi.encode(_uuid, 'numberOfTokenHolders'))); 58 | record.symbol = getString(keccak256(abi.encode(_uuid, 'symbol'))); 59 | } 60 | 61 | return record; 62 | } 63 | 64 | function exists(address _uuid, Ecosystem.Instance memory) external view returns (bool) { 65 | return _exists(_uuid); 66 | } 67 | 68 | /** 69 | * @dev serializes Instance struct 70 | * @param _record Instance 71 | */ 72 | function serialize(Instance memory _record) external preventReentry { 73 | require( 74 | msg.sender == _record.uuid || 75 | msg.sender == _record.ecosystem.daoAddress || 76 | (msg.sender == _record.ecosystem.configuratorAddress && !_exists(_record.uuid)), 77 | 'ElasticDAO: Unauthorized' 78 | ); 79 | 80 | setString(keccak256(abi.encode(_record.uuid, 'name')), _record.name); 81 | setString(keccak256(abi.encode(_record.uuid, 'symbol')), _record.symbol); 82 | setUint(keccak256(abi.encode(_record.uuid, 'eByL')), _record.eByL); 83 | setUint(keccak256(abi.encode(_record.uuid, 'elasticity')), _record.elasticity); 84 | setUint(keccak256(abi.encode(_record.uuid, 'k')), _record.k); 85 | setUint(keccak256(abi.encode(_record.uuid, 'lambda')), _record.lambda); 86 | setUint(keccak256(abi.encode(_record.uuid, 'm')), _record.m); 87 | setUint(keccak256(abi.encode(_record.uuid, 'maxLambdaPurchase')), _record.maxLambdaPurchase); 88 | 89 | setBool(keccak256(abi.encode(_record.uuid, 'exists')), true); 90 | 91 | emit Serialized(_record.uuid); 92 | } 93 | 94 | function updateNumberOfTokenHolders(Instance memory _record, uint256 numberOfTokenHolders) 95 | external 96 | preventReentry 97 | { 98 | setUint(keccak256(abi.encode(_record.uuid, 'numberOfTokenHolders')), numberOfTokenHolders); 99 | } 100 | 101 | function _exists(address _uuid) internal view returns (bool) { 102 | return getBool(keccak256(abi.encode(_uuid, 'exists'))); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /contests/02-elasticdao/contracts/services/Configurator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPLv3 2 | pragma solidity 0.7.2; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import '../models/DAO.sol'; 6 | import '../models/Ecosystem.sol'; 7 | import '../models/Token.sol'; 8 | 9 | import '../tokens/ElasticGovernanceToken.sol'; 10 | 11 | import '@pie-dao/proxy/contracts/PProxy.sol'; 12 | 13 | /** 14 | * @notice This contract is used for configuring ElasticDAOs 15 | * @dev The main reason for having this is to decrease the size of ElasticDAO.sol 16 | */ 17 | contract Configurator { 18 | /** 19 | * @dev creates DAO.Instance record 20 | * @param _summoners addresses of the summoners 21 | * @param _name name of the DAO 22 | * @param _ecosystem instance of Ecosystem the DAO uses 23 | * @param _maxVotingLambda - the maximum amount of lambda that can be used to vote in the DAO 24 | * @return bool true 25 | */ 26 | function buildDAO( 27 | address[] memory _summoners, 28 | string memory _name, 29 | uint256 _maxVotingLambda, 30 | Ecosystem.Instance memory _ecosystem 31 | ) external returns (bool) { 32 | DAO daoStorage = DAO(_ecosystem.daoModelAddress); 33 | DAO.Instance memory dao; 34 | 35 | dao.uuid = msg.sender; 36 | dao.ecosystem = _ecosystem; 37 | dao.maxVotingLambda = _maxVotingLambda; 38 | dao.name = _name; 39 | dao.summoned = false; 40 | dao.summoners = _summoners; 41 | daoStorage.serialize(dao); 42 | 43 | return true; 44 | } 45 | 46 | /** 47 | * @dev duplicates the ecosystem contract address defaults so that each 48 | * deployed DAO has it's own ecosystem configuration 49 | * @param _controller the address which can control the core DAO functions 50 | * @param _defaults instance of Ecosystem with the implementation addresses 51 | * @return ecosystem Ecosystem.Instance 52 | */ 53 | function buildEcosystem(address _controller, Ecosystem.Instance memory _defaults) 54 | external 55 | returns (Ecosystem.Instance memory ecosystem) 56 | { 57 | ecosystem.configuratorAddress = _defaults.configuratorAddress; 58 | ecosystem.daoAddress = msg.sender; 59 | ecosystem.daoModelAddress = _deployProxy(_defaults.daoModelAddress, _controller); 60 | ecosystem.ecosystemModelAddress = _deployProxy(_defaults.ecosystemModelAddress, _controller); 61 | ecosystem.governanceTokenAddress = _deployProxy(_defaults.governanceTokenAddress, _controller); 62 | ecosystem.tokenHolderModelAddress = _deployProxy( 63 | _defaults.tokenHolderModelAddress, 64 | _controller 65 | ); 66 | ecosystem.tokenModelAddress = _deployProxy(_defaults.tokenModelAddress, _controller); 67 | 68 | Ecosystem(ecosystem.ecosystemModelAddress).serialize(ecosystem); 69 | return ecosystem; 70 | } 71 | 72 | /** 73 | * @dev creates a governance token proxy and Token instance (storage) 74 | * @param _controller the address which can control the core DAO functions 75 | * @param _name name of the token 76 | * @param _symbol symbol of the token 77 | * @param _eByL initial ETH/token ratio 78 | * @param _elasticity the percentage by which capitalDelta should increase 79 | * @param _k a constant, initially set by the DAO 80 | * @param _maxLambdaPurchase maximum amount of lambda (shares) that can be 81 | * minted on each call to the join function in ElasticDAO.sol 82 | * @param _ecosystem the DAO's ecosystem instance 83 | * @return token Token.Instance 84 | */ 85 | function buildToken( 86 | address _controller, 87 | string memory _name, 88 | string memory _symbol, 89 | uint256 _eByL, 90 | uint256 _elasticity, 91 | uint256 _k, 92 | uint256 _maxLambdaPurchase, 93 | Ecosystem.Instance memory _ecosystem 94 | ) external returns (Token.Instance memory token) { 95 | Token tokenStorage = Token(_ecosystem.tokenModelAddress); 96 | token.eByL = _eByL; 97 | token.ecosystem = _ecosystem; 98 | token.elasticity = _elasticity; 99 | token.k = _k; 100 | token.lambda = 0; 101 | token.m = 1000000000000000000; 102 | token.maxLambdaPurchase = _maxLambdaPurchase; 103 | token.name = _name; 104 | token.symbol = _symbol; 105 | token.uuid = _ecosystem.governanceTokenAddress; 106 | 107 | // initialize the token within the ecosystem 108 | ElasticGovernanceToken(token.uuid).initialize( 109 | _controller, 110 | _ecosystem.daoAddress, 111 | _ecosystem.ecosystemModelAddress, 112 | _controller 113 | ); 114 | 115 | // serialize ecosystem and token 116 | Ecosystem(_ecosystem.ecosystemModelAddress).serialize(_ecosystem); 117 | tokenStorage.serialize(token); 118 | 119 | return token; 120 | } 121 | 122 | function _deployProxy(address _implementationAddress, address _owner) internal returns (address) { 123 | PProxy proxy = new PProxy(); 124 | proxy.setImplementation(_implementationAddress); 125 | proxy.setProxyOwner(_owner); 126 | return address(proxy); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /contests/02-elasticdao/contracts/libraries/ElasticMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPLv3 2 | pragma solidity 0.7.2; 3 | 4 | import './SafeMath.sol'; 5 | 6 | /** 7 | * @dev Provides functions for performing ElasticDAO specific math. 8 | * 9 | * These functions correspond with functions provided by the JS SDK and should 10 | * always be used instead of doing calculations within other contracts to avoid 11 | * any inconsistencies in the math. 12 | * 13 | * Notes: 14 | * 15 | * - Dash values represent the state after a transaction has completed successfully. 16 | * - Non-dash values represent the current state, before the transaction has completed. 17 | * - Lambda is the math term for shares. We typically expose the value to users as 18 | * shares instead of lambda because it's easier to grok. 19 | */ 20 | library ElasticMath { 21 | /** 22 | * @dev calculates the value of capitalDelta; the amount of ETH backing each 23 | * governance token. 24 | * @param totalEthValue amount of ETH in the DAO contract 25 | * @param totalSupplyOfTokens number of tokens in existance 26 | * 27 | * capitalDelta = totalEthValue / totalSupplyOfTokens 28 | * @return uint256 29 | */ 30 | function capitalDelta(uint256 totalEthValue, uint256 totalSupplyOfTokens) 31 | internal 32 | pure 33 | returns (uint256) 34 | { 35 | return wdiv(totalEthValue, totalSupplyOfTokens); 36 | } 37 | 38 | /** 39 | * @dev calculates the value of deltaE; the amount of ETH required to mint deltaLambda 40 | * @param deltaLambda = lambdaDash - lambda 41 | * @param capitalDeltaValue the ETH/token ratio; see capitalDelta(uint256, uint256) 42 | * @param k constant token multiplier - it increases the number of tokens 43 | * that each member of the DAO has with respect to their lambda 44 | * @param elasticity the percentage by which capitalDelta (cost of entering the DAO) 45 | * should increase on every join 46 | * @param lambda outstanding shares 47 | * @param m - lambda modifier - it's value increases every time someone joins the DAO 48 | * 49 | * lambdaDash = deltaLambda + lambda 50 | * mDash = ( lambdaDash / lambda ) * m 51 | * deltaE = capitalDelta * k * ( lambdaDash * mDash * ( 1 + elasticity ) - lambda * m ) 52 | * @return uint256 53 | */ 54 | function deltaE( 55 | uint256 deltaLambda, 56 | uint256 capitalDeltaValue, 57 | uint256 k, 58 | uint256 elasticity, 59 | uint256 lambda, 60 | uint256 m 61 | ) internal pure returns (uint256) { 62 | uint256 lambdaDash = SafeMath.add(deltaLambda, lambda); 63 | 64 | return 65 | wmul( 66 | wmul(capitalDeltaValue, k), 67 | SafeMath.sub( 68 | wmul(lambdaDash, wmul(mDash(lambdaDash, lambda, m), revamp(elasticity))), 69 | wmul(lambda, m) 70 | ) 71 | ); 72 | } 73 | 74 | /** 75 | * @dev calculates the lambda value given t, k, & m 76 | * @param tokens t value; number of tokens for which lambda should be calculated 77 | * @param k constant token multiplier - it increases the number of tokens 78 | * that each member of the DAO has with respect to their lambda 79 | * @param m - lambda modifier - it's value increases every time someone joins the DAO 80 | * 81 | * lambda = t / ( m * k) 82 | * @return uint256 83 | */ 84 | function lambdaFromT( 85 | uint256 tokens, 86 | uint256 k, 87 | uint256 m 88 | ) internal pure returns (uint256) { 89 | return wdiv(tokens, wmul(k, m)); 90 | } 91 | 92 | /** 93 | * @dev calculates the future share modifier given the future value of 94 | * lambda (lambdaDash), the current value of lambda, and the current share modifier 95 | * @param m current share modifier 96 | * @param lambda current outstanding shares 97 | * @param lambdaDash future outstanding shares 98 | * 99 | * mDash = ( lambdaDash / lambda ) * m 100 | * @return uint256 101 | */ 102 | function mDash( 103 | uint256 lambdaDash, 104 | uint256 lambda, 105 | uint256 m 106 | ) internal pure returns (uint256) { 107 | return wmul(wdiv(lambdaDash, lambda), m); 108 | } 109 | 110 | /** 111 | * @dev calculates the value of revamp 112 | * @param elasticity the percentage by which capitalDelta should increase 113 | * 114 | * revamp = 1 + elasticity 115 | * @return uint256 116 | */ 117 | function revamp(uint256 elasticity) internal pure returns (uint256) { 118 | return SafeMath.add(elasticity, SafeMath.pow(10, 18)); 119 | } 120 | 121 | /** 122 | * @dev calculates the number of tokens represented by lambda given k & m 123 | * @param lambda shares 124 | * @param k a constant, initially set by the DAO 125 | * @param m share modifier 126 | * 127 | * t = lambda * m * k 128 | * @return uint256 129 | */ 130 | function t( 131 | uint256 lambda, 132 | uint256 k, 133 | uint256 m 134 | ) internal pure returns (uint256) { 135 | return wmul(wmul(lambda, k), m); 136 | } 137 | 138 | /** 139 | * @dev multiplies two float values, required since solidity does not handle 140 | * floating point values 141 | * 142 | * inspiration: https://github.com/dapphub/ds-math/blob/master/src/math.sol 143 | * 144 | * @return uint256 145 | */ 146 | function wmul(uint256 a, uint256 b) internal pure returns (uint256) { 147 | return SafeMath.add(SafeMath.mul(a, b), 1000000000000000000 / 2) / 1000000000000000000; 148 | } 149 | 150 | /** 151 | * @dev divides two float values, required since solidity does not handle 152 | * floating point values. 153 | * 154 | * inspiration: https://github.com/dapphub/ds-math/blob/master/src/math.sol 155 | * 156 | * @return uint256 157 | */ 158 | function wdiv(uint256 a, uint256 b) internal pure returns (uint256) { 159 | return SafeMath.add(SafeMath.mul(a, 1000000000000000000), b / 2) / b; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /contests/02-elasticdao/contracts/core/ElasticDAOFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPLv3 2 | pragma solidity 0.7.2; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import './ElasticDAO.sol'; 6 | 7 | import '../models/Ecosystem.sol'; 8 | import '../services/ReentryProtection.sol'; 9 | 10 | import '@pie-dao/proxy/contracts/PProxy.sol'; 11 | 12 | /** 13 | * @dev The factory contract for ElasticDAO 14 | * Deploys ElasticDAO's and also sets all the required parameters and permissions, 15 | * Collects a fee which is later used by ELasticDAO for further development of the project. 16 | */ 17 | contract ElasticDAOFactory is ReentryProtection { 18 | address public ecosystemModelAddress; 19 | address public elasticDAOImplementationAddress; 20 | address public manager; 21 | address payable feeAddress; 22 | address[] public deployedDAOAddresses; 23 | uint256 public deployedDAOCount; 24 | uint256 public fee; 25 | bool public initialized = false; 26 | 27 | event DeployedDAO(address indexed daoAddress); 28 | event ElasticDAOImplementationAddressUpdated(address indexed elasticDAOImplementationAddress); 29 | event FeeAddressUpdated(address indexed feeReceiver); 30 | event FeesCollected(address indexed feeAddress, uint256 amount); 31 | event FeeUpdated(uint256 amount); 32 | event ManagerUpdated(address indexed newManager); 33 | 34 | modifier onlyManager() { 35 | require(manager == msg.sender, 'ElasticDAO: Only manager'); 36 | _; 37 | } 38 | 39 | /** 40 | * @notice Initializes the ElasticDAO factory 41 | * 42 | * @param _ecosystemModelAddress - the address of the ecosystem model 43 | * @dev 44 | * Requirements: 45 | * - The factory cannot already be initialized 46 | * - The ecosystem model address cannot be the zero address 47 | */ 48 | function initialize(address _ecosystemModelAddress, address _elasticDAOImplementationAddress) 49 | external 50 | preventReentry 51 | { 52 | require(initialized == false, 'ElasticDAO: Factory already initialized'); 53 | require( 54 | _ecosystemModelAddress != address(0) && _elasticDAOImplementationAddress != address(0), 55 | 'ElasticDAO: Address Zero' 56 | ); 57 | 58 | deployedDAOCount = 0; 59 | ecosystemModelAddress = _ecosystemModelAddress; 60 | elasticDAOImplementationAddress = _elasticDAOImplementationAddress; 61 | fee = 250000000000000000; 62 | initialized = true; 63 | manager = msg.sender; 64 | } 65 | 66 | /** 67 | * @notice collects the fees sent to this contract 68 | * 69 | * @dev emits FeesCollected event 70 | * Requirement: 71 | * - The fee collection transaction should be successful 72 | */ 73 | function collectFees() external preventReentry { 74 | uint256 amount = address(this).balance; 75 | 76 | (bool success, ) = feeAddress.call{ value: amount }(''); 77 | require(success, 'ElasticDAO: TransactionFailed'); 78 | emit FeesCollected(address(feeAddress), amount); 79 | } 80 | 81 | /** 82 | * @notice deploys DAO and initializes token and stores the address of the deployed DAO 83 | * 84 | * @param _summoners - an array containing address of summoners 85 | * @param _nameOfDAO - the name of the DAO 86 | * @param _nameOfToken - the name of the token 87 | * @param _eByL-the amount of lambda a summoner gets(per ETH) during the seeding phase of the DAO 88 | * @param _elasticity-the value by which the cost of entering the DAO increases ( on every join ) 89 | * @param _k - is the constant token multiplier, 90 | * it increases the number of tokens that each member of the DAO has with respect to their lambda 91 | * @param _maxLambdaPurchase - is the maximum amount of lambda that can be purchased per wallet 92 | * @param _maxVotingLambda - is the maximum amount of lambda that can be used to vote 93 | * 94 | * @dev emits DeployedDAO event 95 | * @dev 96 | * Requirement: 97 | * - The fee required should be sent in the call to the function 98 | */ 99 | function deployDAOAndToken( 100 | address[] memory _summoners, 101 | string memory _nameOfDAO, 102 | string memory _nameOfToken, 103 | string memory _symbol, 104 | uint256 _eByL, 105 | uint256 _elasticity, 106 | uint256 _k, 107 | uint256 _maxLambdaPurchase, 108 | uint256 _maxVotingLambda 109 | ) external payable preventReentry { 110 | require(fee == msg.value, 'ElasticDAO: A fee is required to deploy a DAO'); 111 | 112 | // Deploy the DAO behind PProxy 113 | PProxy proxy = new PProxy(); 114 | proxy.setImplementation(elasticDAOImplementationAddress); 115 | proxy.setProxyOwner(msg.sender); 116 | 117 | address payable daoAddress = address(proxy); 118 | 119 | // initialize the DAO 120 | ElasticDAO(daoAddress).initialize( 121 | ecosystemModelAddress, 122 | msg.sender, 123 | _summoners, 124 | _nameOfDAO, 125 | _maxVotingLambda 126 | ); 127 | 128 | deployedDAOAddresses.push(daoAddress); 129 | deployedDAOCount = SafeMath.add(deployedDAOCount, 1); 130 | 131 | // initialize the token 132 | ElasticDAO(daoAddress).initializeToken( 133 | _nameOfToken, 134 | _symbol, 135 | _eByL, 136 | _elasticity, 137 | _k, 138 | _maxLambdaPurchase 139 | ); 140 | emit DeployedDAO(daoAddress); 141 | } 142 | 143 | /** 144 | * @notice updates the address of the elasticDAO implementation 145 | * @param _elasticDAOImplementationAddress - the new address 146 | * @dev emits ElasticDAOImplementationAddressUpdated event 147 | * @dev Requirement: 148 | * - The elasticDAO implementation address cannot be zero address 149 | */ 150 | function updateElasticDAOImplementationAddress(address _elasticDAOImplementationAddress) 151 | external 152 | onlyManager 153 | preventReentry 154 | { 155 | require(_elasticDAOImplementationAddress != address(0), 'ElasticDAO: Address Zero'); 156 | 157 | elasticDAOImplementationAddress = _elasticDAOImplementationAddress; 158 | emit ElasticDAOImplementationAddressUpdated(_elasticDAOImplementationAddress); 159 | } 160 | 161 | /** 162 | * @notice updates the fee required to deploy a DAQ 163 | * 164 | * @param _amount - the new amount of the fees 165 | * 166 | * @dev emits FeeUpdated event 167 | */ 168 | function updateFee(uint256 _amount) external onlyManager preventReentry { 169 | fee = _amount; 170 | emit FeeUpdated(fee); 171 | } 172 | 173 | /** 174 | * @notice updates the address of the fee reciever 175 | * 176 | * @param _feeReceiver - the new address of the fee reciever 177 | * 178 | * @dev emits FeeUpdated event 179 | * @dev 180 | * Requirement: 181 | * - The fee receiver address cannot be zero address 182 | */ 183 | function updateFeeAddress(address _feeReceiver) external onlyManager preventReentry { 184 | require(_feeReceiver != address(0), 'ElasticDAO: Address Zero'); 185 | 186 | feeAddress = payable(_feeReceiver); 187 | emit FeeAddressUpdated(_feeReceiver); 188 | } 189 | 190 | /** 191 | * @notice updates the manager address 192 | * 193 | * @param _newManager - the address of the new manager 194 | * 195 | * @dev Requirement 196 | * - Address of the manager cannot be zero 197 | * @dev emits ManagerUpdated event 198 | */ 199 | function updateManager(address _newManager) external onlyManager preventReentry { 200 | require(_newManager != address(0), 'ElasticDAO: Address Zero'); 201 | 202 | manager = _newManager; 203 | emit ManagerUpdated(manager); 204 | } 205 | 206 | receive() external payable {} 207 | 208 | fallback() external payable {} 209 | } 210 | -------------------------------------------------------------------------------- /contests/01-slingshot/contracts/Slingshot.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: None 2 | pragma solidity >=0.7.5; 3 | pragma abicoder v2; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | import "@openzeppelin/contracts/math/SafeMath.sol"; 7 | import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; 8 | import "./Adminable.sol"; 9 | import "./lib/Strings.sol"; 10 | import "./ModuleRegistry.sol"; 11 | 12 | interface IWETH is IERC20 { 13 | function deposit() external payable; 14 | function balanceOf(uint amount) external returns (uint); 15 | function withdraw(uint amount) external; 16 | } 17 | 18 | /// @title Slingshot Trading Contract 19 | /// @author DEXAG, Inc. 20 | /// @notice This contract serves as the entrypoint for executing 21 | /// a Slingshot transaction on-chain. 22 | /// @dev The specific logic for each DEX/AMM is defined within its 23 | /// own corresponding module that is stored in the module registry. 24 | /// Slingshot.sol references these modules to appropriately execute a trade. 25 | /// Slingshot.sol also performs some safety checks to account for slippage 26 | /// and security. Slingshot.sol depends on the Slingshot backend to provide 27 | /// the details of how a given transaction will be executed within a 28 | /// particular market. 29 | contract Slingshot is Adminable, Strings { 30 | using SafeMath for uint256; 31 | using SafeERC20 for IERC20; 32 | 33 | address public ETH_ADDRESS; 34 | IWETH public WETH; 35 | 36 | struct TradeFormat { 37 | address moduleAddress; 38 | bytes encodedCalldata; 39 | } 40 | 41 | ModuleRegistry public moduleRegistry; 42 | 43 | event Trade( 44 | address indexed user, 45 | address fromToken, 46 | address toToken, 47 | uint fromAmount, 48 | uint toAmount, 49 | address recipient 50 | ); 51 | event NewModuleRegistry(address oldRegistry, address newRegistry); 52 | 53 | /// @notice Use this function for post upgrade setup 54 | /// @param _admin Address to control admin functions 55 | function postUpgrade(address _admin) external onlyAdminIfInitialized { 56 | // skip when initialized 57 | if (!isInitialized()) initialize(_admin); 58 | // all other post upgrade setup below 59 | } 60 | 61 | /// @notice Initialization function for proxy setup 62 | /// @param _admin Address to control admin functions 63 | function initialize(address _admin) internal initializer { 64 | initializeAdmin(_admin); 65 | ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; 66 | WETH = IWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); 67 | } 68 | 69 | /// @notice Executes multi-hop trades to get the best result 70 | /// @param fromToken Start token address 71 | /// @param toToken Target token address 72 | /// @param fromAmount The initial amount of fromToken to start trading with 73 | /// @param trades Array of encoded trades that are atomically executed 74 | /// @param finalAmountMin The minimum expected output after all trades have been executed 75 | /// @param recipient The address that will receive the output of a trade 76 | function executeTrades( 77 | address fromToken, 78 | address toToken, 79 | uint fromAmount, 80 | TradeFormat[] calldata trades, 81 | uint finalAmountMin, 82 | address recipient 83 | ) public payable { 84 | uint initialBalance = _getTokenBalance(toToken); 85 | _transferFromOrWrap(fromToken, _msgSender(), fromAmount); 86 | // Checks to make sure that module exists and is correct 87 | for(uint i = 0; i < trades.length; i++) { 88 | require(moduleRegistry.isModule(trades[i].moduleAddress), "Slingshot: not a module"); 89 | // delagatecall message is made on module contract, which is trusted 90 | (bool success, bytes memory data) = trades[i].moduleAddress.delegatecall(trades[i].encodedCalldata); 91 | 92 | require(success, appendString(prependNumber(i, ", Slingshot: swap failed: "), string(data))); 93 | } 94 | 95 | uint finalBalance = _getTokenBalance(toToken); 96 | uint finalAmount = finalBalance.sub(initialBalance); 97 | 98 | require(finalAmount >= finalAmountMin, "Slingshot: result is lower than required min"); 99 | 100 | // Send to recipient address. Generally expected to be msg.sender, but can also be defined by user. 101 | // This allows for more flexibility, but isn't necessary. 102 | _sendFunds(toToken, recipient, finalAmount); 103 | 104 | emit Trade(_msgSender(), fromToken, toToken, fromAmount, finalAmount, recipient); 105 | } 106 | 107 | /// @notice Sets module registry used to verify modules 108 | /// @param _moduleRegistry The address of module registry 109 | function setModuleRegistry(address _moduleRegistry) external onlyAdmin { 110 | address oldRegistry = address(moduleRegistry); 111 | moduleRegistry = ModuleRegistry(_moduleRegistry); 112 | emit NewModuleRegistry(oldRegistry, _moduleRegistry); 113 | } 114 | 115 | /// @notice In an unlikely scenario of tokens being send to this contract 116 | /// allow adming to rescue them. 117 | /// @param token The address of the token to rescue 118 | /// @param to The address of recipient 119 | /// @param amount The amount of the token to rescue 120 | function rescueTokens(address token, address to, uint amount) external onlyAdmin { 121 | if (token == ETH_ADDRESS) { 122 | (bool success, ) = to.call{value: amount}(""); 123 | require(success, "Slingshot: ETH rescue failed."); 124 | } else { 125 | IERC20(token).safeTransfer(to, amount); 126 | } 127 | } 128 | 129 | /// @notice Transfer tokens from sender to this contract or wraps ETH 130 | /// @param fromToken The address of the token 131 | /// @param from The address of sender that provides token 132 | /// @param amount The amount of the token to transfer 133 | function _transferFromOrWrap(address fromToken, address from, uint amount) internal { 134 | // transfer tokens or wrap ETH 135 | if (fromToken == ETH_ADDRESS) { 136 | require(msg.value == amount, "Slingshot: incorect ETH value"); 137 | WETH.deposit{value: amount}(); 138 | } else { 139 | require(msg.value == 0, "Slingshot: should not send ETH"); 140 | IERC20(fromToken).safeTransferFrom(from, address(this), amount); 141 | } 142 | } 143 | 144 | /// @notice Returns balancer of the token 145 | /// @param token The address of the token 146 | /// @return balance of the token, defaults to WETH for ETH 147 | function _getTokenBalance(address token) internal view returns (uint) { 148 | if (token == ETH_ADDRESS) { 149 | return WETH.balanceOf(address(this)); 150 | } else { 151 | return IERC20(token).balanceOf(address(this)); 152 | } 153 | } 154 | 155 | /// @notice Sends token funds. For ETH, it unwraps WETH 156 | /// @param token The address of the token to send 157 | /// @param to The address of recipient 158 | /// @param amount The amount of the token to send 159 | function _sendFunds(address token, address to, uint amount) internal { 160 | if (token == ETH_ADDRESS) { 161 | WETH.withdraw(amount); 162 | (bool success, ) = to.call{value: amount}(""); 163 | require(success, "Slingshot: ETH Transfer failed."); 164 | } else { 165 | IERC20(token).safeTransfer(to, amount); 166 | } 167 | } 168 | 169 | receive() external payable {} 170 | } 171 | -------------------------------------------------------------------------------- /contests/02-elasticdao/contracts/core/ElasticDAO.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPLv3 2 | pragma solidity 0.7.2; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import '../libraries/ElasticMath.sol'; 6 | import '../libraries/SafeMath.sol'; 7 | 8 | import '../models/DAO.sol'; 9 | import '../models/Ecosystem.sol'; 10 | import '../models/Token.sol'; 11 | 12 | import '../services/Configurator.sol'; 13 | import '../services/ReentryProtection.sol'; 14 | 15 | import '@pie-dao/proxy/contracts/PProxy.sol'; 16 | 17 | /** 18 | * @dev The ElasticDAO contract outlines and defines all the functionality 19 | * such as initialize, Join, exit, etc for an elasticDAO. 20 | * 21 | * It also serves as the vault for ElasticDAO. 22 | */ 23 | contract ElasticDAO is ReentryProtection { 24 | address public deployer; 25 | address public ecosystemModelAddress; 26 | address public controller; 27 | address[] public summoners; 28 | bool public initialized; 29 | 30 | event ElasticGovernanceTokenDeployed(address indexed tokenAddress); 31 | event MaxVotingLambdaChanged(address indexed daoAddress, bytes32 settingName, uint256 value); 32 | event ControllerChanged(address indexed daoAddress, bytes32 settingName, address value); 33 | event ExitDAO( 34 | address indexed daoAddress, 35 | address indexed memberAddress, 36 | uint256 shareAmount, 37 | uint256 ethAmount 38 | ); 39 | event JoinDAO( 40 | address indexed daoAddress, 41 | address indexed memberAddress, 42 | uint256 shareAmount, 43 | uint256 ethAmount 44 | ); 45 | event SeedDAO(address indexed daoAddress, address indexed summonerAddress, uint256 amount); 46 | event SummonedDAO(address indexed daoAddress, address indexed summonedBy); 47 | 48 | modifier onlyAfterSummoning() { 49 | DAO.Instance memory dao = _getDAO(); 50 | require(dao.summoned, 'ElasticDAO: DAO must be summoned'); 51 | _; 52 | } 53 | modifier onlyAfterTokenInitialized() { 54 | Ecosystem.Instance memory ecosystem = _getEcosystem(); 55 | bool tokenInitialized = 56 | Token(_getEcosystem().tokenModelAddress).exists(ecosystem.governanceTokenAddress, ecosystem); 57 | require(tokenInitialized, 'ElasticDAO: Please call initializeToken first'); 58 | _; 59 | } 60 | modifier onlyBeforeSummoning() { 61 | DAO.Instance memory dao = _getDAO(); 62 | require(dao.summoned == false, 'ElasticDAO: DAO must not be summoned'); 63 | _; 64 | } 65 | modifier onlyController() { 66 | require(msg.sender == controller, 'ElasticDAO: Only controller'); 67 | _; 68 | } 69 | modifier onlyDeployer() { 70 | require(msg.sender == deployer, 'ElasticDAO: Only deployer'); 71 | _; 72 | } 73 | modifier onlySummoners() { 74 | Ecosystem.Instance memory ecosystem = _getEcosystem(); 75 | DAO daoContract = DAO(ecosystem.daoModelAddress); 76 | DAO.Instance memory dao = daoContract.deserialize(address(this), ecosystem); 77 | bool summonerCheck = daoContract.isSummoner(dao, msg.sender); 78 | 79 | require(summonerCheck, 'ElasticDAO: Only summoners'); 80 | _; 81 | } 82 | modifier onlyWhenOpen() { 83 | require(address(this).balance > 0, 'ElasticDAO: This DAO is closed'); 84 | _; 85 | } 86 | 87 | /** 88 | * @notice Initializes and builds the ElasticDAO struct by passing and initializing 89 | * all the required parameters into the configurator 90 | * 91 | * @param _ecosystemModelAddress - the address of the ecosystem model 92 | * @param _controller the address which can control the core DAO functions 93 | * @param _summoners - an array containing the addresses of the summoners 94 | * @param _name - the name of the DAO 95 | * @param _maxVotingLambda - the maximum amount of lambda that can be used to vote in the DAO 96 | * 97 | * @dev 98 | * Requirements: 99 | * - The DAO cannot already be initialized 100 | * - The ecosystem model address cannot be the zero address 101 | * - The DAO must have atleast one summoner to summon the DAO 102 | * - The configurator should be able to successfully build the DAO 103 | */ 104 | function initialize( 105 | address _ecosystemModelAddress, 106 | address _controller, 107 | address[] memory _summoners, 108 | string memory _name, 109 | uint256 _maxVotingLambda 110 | ) external preventReentry { 111 | require(initialized == false, 'ElasticDAO: Already initialized'); 112 | require( 113 | _ecosystemModelAddress != address(0) || _controller != address(0), 114 | 'ElasticDAO: Address Zero' 115 | ); 116 | require(_summoners.length > 0, 'ElasticDAO: At least 1 summoner required'); 117 | 118 | Ecosystem.Instance memory defaults = Ecosystem(_ecosystemModelAddress).deserialize(address(0)); 119 | Configurator configurator = Configurator(defaults.configuratorAddress); 120 | Ecosystem.Instance memory ecosystem = configurator.buildEcosystem(controller, defaults); 121 | ecosystemModelAddress = ecosystem.ecosystemModelAddress; 122 | 123 | controller = _controller; 124 | deployer = msg.sender; 125 | summoners = _summoners; 126 | 127 | bool success = configurator.buildDAO(_summoners, _name, _maxVotingLambda, ecosystem); 128 | initialized = true; 129 | require(success, 'ElasticDAO: Build DAO Failed'); 130 | } 131 | 132 | /** 133 | * @notice initializes the token of the DAO, using the Configurator 134 | * 135 | * @param _name - name of the token 136 | * @param _symbol - symbol of the token 137 | * @param _eByL -the amount of lambda a summoner gets(per ETH) during the seeding phase of the DAO 138 | * @param _elasticity the value by which the cost of entering the DAO increases ( on every join ) 139 | * @param _k - is the constant token multiplier 140 | * it increases the number of tokens that each member of the DAO has with respect to their lambda 141 | * @param _maxLambdaPurchase - is the maximum amount of lambda that can be purchased per wallet 142 | * 143 | * @dev refer https://docs.openzeppelin.com/cli/2.8/deploying-with-create2#create2 144 | * for further understanding of Create2 and salt 145 | * @dev emits ElasticGovernanceTokenDeployed event 146 | * @dev 147 | * Requirements: 148 | * - Only the deployer of the DAO can initialize the Token 149 | * - The controller of the DAO should successfully be set as the burner of the tokens of the DAO 150 | * - The controller of the DAO should successfully be set as the minter of the tokens of the DAO 151 | */ 152 | function initializeToken( 153 | string memory _name, 154 | string memory _symbol, 155 | uint256 _eByL, 156 | uint256 _elasticity, 157 | uint256 _k, 158 | uint256 _maxLambdaPurchase 159 | ) external onlyBeforeSummoning onlyDeployer preventReentry { 160 | require(msg.sender == deployer, 'ElasticDAO: Only deployer can initialize the Token'); 161 | Ecosystem.Instance memory ecosystem = _getEcosystem(); 162 | 163 | Token.Instance memory token = 164 | Configurator(ecosystem.configuratorAddress).buildToken( 165 | controller, 166 | _name, 167 | _symbol, 168 | _eByL, 169 | _elasticity, 170 | _k, 171 | _maxLambdaPurchase, 172 | ecosystem 173 | ); 174 | 175 | emit ElasticGovernanceTokenDeployed(token.uuid); 176 | } 177 | 178 | /** 179 | * @notice this function is to be used for exiting the DAO 180 | * for the underlying ETH value of _deltaLambda 181 | * 182 | * The eth value of _deltaLambda is calculated using: 183 | * 184 | * eth to be transfered = ( deltaLambda/lambda ) * totalEthInTheDAO 185 | * 186 | * @param _deltaLambda - the amount of lambda the address exits with 187 | * 188 | * Requirement: 189 | * - Should be able to successfully exit the DAO 190 | * @dev emits ExitDAO event 191 | */ 192 | function exit(uint256 _deltaLambda) external onlyAfterSummoning preventReentry { 193 | // burn the shares 194 | Token.Instance memory token = _getToken(); 195 | ElasticGovernanceToken tokenContract = ElasticGovernanceToken(token.uuid); 196 | 197 | // eth to be transfered = ( deltaLambda/lambda ) * totalEthInTheDAO 198 | uint256 ratioOfShares = ElasticMath.wdiv(_deltaLambda, token.lambda); 199 | uint256 ethToBeTransfered = ElasticMath.wmul(ratioOfShares, address(this).balance); 200 | // transfer the eth 201 | tokenContract.burnShares(msg.sender, _deltaLambda); 202 | (bool success, ) = msg.sender.call{ value: ethToBeTransfered }(''); 203 | require(success, 'ElasticDAO: Exit Failed'); 204 | emit ExitDAO(address(this), msg.sender, _deltaLambda, ethToBeTransfered); 205 | } 206 | 207 | /** 208 | * @notice this function is used to join the DAO after it has been summoned 209 | * Joining the DAO is syntactically equal to minting _deltaLambda for the function caller. 210 | * 211 | * Based on the current state of the DAO, capitalDelta, deltaE, mDash are calulated, 212 | * after which _deltaLambda is minted for the address calling the function. 213 | * 214 | * @param _deltaLambda - the amount of lambda the address joins with 215 | * 216 | * @dev documentation and further math regarding capitalDelta, deltaE, 217 | * mDash can be found at ../libraries/ElasticMath.sol 218 | * @dev emits the JoinDAO event 219 | * 220 | * @dev Requirements: 221 | * The amount of shares being purchased has to be lower than maxLambdaPurchase 222 | * (The value of maxLambdaPurchase is set during the initialzing of the DAO) 223 | * The correct value of ETH, calculated via deltaE, 224 | * must be sent in the transaction by the calling address 225 | * The token contract should be successfully be able to mint _deltaLambda 226 | */ 227 | function join(uint256 _deltaLambda) 228 | external 229 | payable 230 | onlyAfterSummoning 231 | onlyWhenOpen 232 | preventReentry 233 | { 234 | Token.Instance memory token = _getToken(); 235 | 236 | require( 237 | _deltaLambda <= token.maxLambdaPurchase, 238 | 'ElasticDAO: Cannot purchase those many lambda at once' 239 | ); 240 | 241 | ElasticGovernanceToken tokenContract = ElasticGovernanceToken(token.uuid); 242 | uint256 capitalDelta = 243 | ElasticMath.capitalDelta( 244 | // the current totalBalance of the DAO is inclusive of msg.value, 245 | // capitalDelta is to be calculated without the msg.value 246 | address(this).balance - msg.value, 247 | tokenContract.totalSupply() 248 | ); 249 | uint256 deltaE = 250 | ElasticMath.deltaE( 251 | _deltaLambda, 252 | capitalDelta, 253 | token.k, 254 | token.elasticity, 255 | token.lambda, 256 | token.m 257 | ); 258 | 259 | if (deltaE != msg.value) { 260 | revert('ElasticDAO: Incorrect ETH amount'); 261 | } 262 | 263 | // mdash 264 | uint256 lambdaDash = SafeMath.add(_deltaLambda, token.lambda); 265 | uint256 mDash = ElasticMath.mDash(lambdaDash, token.lambda, token.m); 266 | 267 | // serialize the token 268 | Ecosystem.Instance memory ecosystem = _getEcosystem(); 269 | Token tokenStorage = Token(ecosystem.tokenModelAddress); 270 | token.m = mDash; 271 | tokenStorage.serialize(token); 272 | 273 | // tokencontract mint shares 274 | bool success = tokenContract.mintShares(msg.sender, _deltaLambda); 275 | require(success, 'ElasticDAO: Mint Shares Failed during Join'); 276 | emit JoinDAO(address(this), msg.sender, _deltaLambda, msg.value); 277 | } 278 | 279 | /** 280 | * @notice penalizes @param _addresess with @param _amounts respectively 281 | * 282 | * @param _addresses - an array of addresses 283 | * @param _amounts - an array containing the amounts each address has to be penalized respectively 284 | * 285 | * @dev Requirement: 286 | * - Each address must have a corresponding amount to be penalized with 287 | */ 288 | function penalize(address[] memory _addresses, uint256[] memory _amounts) 289 | external 290 | onlyController 291 | preventReentry 292 | { 293 | require( 294 | _addresses.length == _amounts.length, 295 | 'ElasticDAO: An amount is required for each address' 296 | ); 297 | 298 | ElasticGovernanceToken tokenContract = ElasticGovernanceToken(_getToken().uuid); 299 | 300 | for (uint256 i = 0; i < _addresses.length; i += 1) { 301 | tokenContract.burnShares(_addresses[i], _amounts[i]); 302 | } 303 | } 304 | 305 | /** 306 | * @notice rewards @param _addresess with @param _amounts respectively 307 | * 308 | * @param _addresses - an array of addresses 309 | * @param _amounts - an array containing the amounts each address has to be rewarded respectively 310 | * 311 | * @dev Requirement: 312 | * - Each address must have a corresponding amount to be rewarded with 313 | */ 314 | function reward(address[] memory _addresses, uint256[] memory _amounts) 315 | external 316 | onlyController 317 | preventReentry 318 | { 319 | require( 320 | _addresses.length == _amounts.length, 321 | 'ElasticDAO: An amount is required for each address' 322 | ); 323 | 324 | ElasticGovernanceToken tokenContract = ElasticGovernanceToken(_getToken().uuid); 325 | 326 | for (uint256 i = 0; i < _addresses.length; i += 1) { 327 | tokenContract.mintShares(_addresses[i], _amounts[i]); 328 | } 329 | } 330 | 331 | /** 332 | * @notice sets the controller of the DAO, 333 | * The controller of the DAO handles various responsibilities of the DAO, 334 | * such as burning and minting tokens on behalf of the DAO 335 | * 336 | * @param _controller - the new address of the controller of the DAO 337 | * 338 | * @dev emits ControllerChanged event 339 | * @dev Requirements: 340 | * - The controller must not be the 0 address 341 | * - The controller of the DAO should successfully be set as the burner of the tokens of the DAO 342 | * - The controller of the DAO should successfully be set as the minter of the tokens of the DAO 343 | */ 344 | function setController(address _controller) external onlyController preventReentry { 345 | require(_controller != address(0), 'ElasticDAO: Address Zero'); 346 | 347 | controller = _controller; 348 | 349 | // Update minter / burner 350 | ElasticGovernanceToken tokenContract = ElasticGovernanceToken(_getToken().uuid); 351 | bool success = tokenContract.setBurner(controller); 352 | require(success, 'ElasticDAO: Set Burner failed during setController'); 353 | success = tokenContract.setMinter(controller); 354 | require(success, 'ElasticDAO: Set Minter failed during setController'); 355 | 356 | emit ControllerChanged(address(this), 'setController', controller); 357 | } 358 | 359 | /** 360 | * @notice sets the max voting lambda value for the DAO 361 | * @param _maxVotingLambda - the value of the maximum amount of lambda that can be used for voting 362 | * @dev emits MaxVotingLambda event 363 | */ 364 | function setMaxVotingLambda(uint256 _maxVotingLambda) external onlyController preventReentry { 365 | Ecosystem.Instance memory ecosystem = _getEcosystem(); 366 | DAO daoStorage = DAO(ecosystem.daoModelAddress); 367 | DAO.Instance memory dao = daoStorage.deserialize(address(this), ecosystem); 368 | dao.maxVotingLambda = _maxVotingLambda; 369 | daoStorage.serialize(dao); 370 | 371 | emit MaxVotingLambdaChanged(address(this), 'setMaxVotingLambda', _maxVotingLambda); 372 | } 373 | 374 | /** 375 | * @notice seeds the DAO, 376 | * Essentially transferring of ETH by a summoner address, in return for lambda is seeding the DAO, 377 | * The lambda receieved is given by: 378 | * Lambda = Eth / eByL 379 | * 380 | * @dev seeding of the DAO occurs after the DAO has been initialized, 381 | * and before the DAO has been summoned 382 | * @dev emits the SeedDAO event 383 | */ 384 | function seedSummoning() 385 | external 386 | payable 387 | onlyBeforeSummoning 388 | onlySummoners 389 | onlyAfterTokenInitialized 390 | preventReentry 391 | { 392 | Token.Instance memory token = _getToken(); 393 | 394 | uint256 deltaE = msg.value; 395 | uint256 deltaLambda = ElasticMath.wdiv(deltaE, token.eByL); 396 | ElasticGovernanceToken(token.uuid).mintShares(msg.sender, deltaLambda); 397 | 398 | emit SeedDAO(address(this), msg.sender, deltaLambda); 399 | } 400 | 401 | /** 402 | * @notice summons the DAO, 403 | * Summoning the DAO results in all summoners getting _deltaLambda 404 | * after which people can enter the DAO using the join function 405 | * 406 | * @param _deltaLambda - the amount of lambda each summoner address receieves 407 | * 408 | * @dev emits SummonedDAO event 409 | * @dev Requirement: 410 | * The DAO must be seeded with ETH during the seeding phase 411 | * (This is to facilitate capitalDelta calculations after the DAO has been summoned). 412 | * 413 | * @dev documentation and further math regarding capitalDelta 414 | * can be found at ../libraries/ElasticMath.sol 415 | */ 416 | function summon(uint256 _deltaLambda) external onlyBeforeSummoning onlySummoners preventReentry { 417 | require(address(this).balance > 0, 'ElasticDAO: Please seed DAO with ETH to set ETH:EGT ratio'); 418 | 419 | Ecosystem.Instance memory ecosystem = _getEcosystem(); 420 | DAO daoContract = DAO(ecosystem.daoModelAddress); 421 | DAO.Instance memory dao = daoContract.deserialize(address(this), ecosystem); 422 | Token.Instance memory token = 423 | Token(ecosystem.tokenModelAddress).deserialize(ecosystem.governanceTokenAddress, ecosystem); 424 | ElasticGovernanceToken tokenContract = ElasticGovernanceToken(token.uuid); 425 | 426 | // number of summoners can not grow unboundly. it is fixed limit. 427 | for (uint256 i = 0; i < dao.numberOfSummoners; i += 1) { 428 | tokenContract.mintShares(daoContract.getSummoner(dao, i), _deltaLambda); 429 | } 430 | dao.summoned = true; 431 | daoContract.serialize(dao); 432 | 433 | emit SummonedDAO(address(this), msg.sender); 434 | } 435 | 436 | // Getters 437 | 438 | function getDAO() external view returns (DAO.Instance memory) { 439 | return _getDAO(); 440 | } 441 | 442 | function getEcosystem() external view returns (Ecosystem.Instance memory) { 443 | return _getEcosystem(); 444 | } 445 | 446 | // Private 447 | 448 | function _getDAO() internal view returns (DAO.Instance memory) { 449 | Ecosystem.Instance memory ecosystem = _getEcosystem(); 450 | return DAO(ecosystem.daoModelAddress).deserialize(address(this), ecosystem); 451 | } 452 | 453 | function _getEcosystem() internal view returns (Ecosystem.Instance memory) { 454 | return Ecosystem(ecosystemModelAddress).deserialize(address(this)); 455 | } 456 | 457 | function _getToken() internal view returns (Token.Instance memory) { 458 | Ecosystem.Instance memory ecosystem = _getEcosystem(); 459 | return 460 | Token(ecosystem.tokenModelAddress).deserialize(ecosystem.governanceTokenAddress, ecosystem); 461 | } 462 | 463 | receive() external payable {} 464 | 465 | fallback() external payable {} 466 | } 467 | -------------------------------------------------------------------------------- /contests/02-elasticdao/contracts/tokens/ElasticGovernanceToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPLv3 2 | pragma solidity 0.7.2; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import '../interfaces/IElasticToken.sol'; 6 | 7 | import '../libraries/SafeMath.sol'; 8 | import '../libraries/ElasticMath.sol'; 9 | 10 | import '../core/ElasticDAO.sol'; 11 | import '../models/DAO.sol'; 12 | import '../models/Ecosystem.sol'; 13 | import '../models/Token.sol'; 14 | import '../models/TokenHolder.sol'; 15 | 16 | import '../services/ReentryProtection.sol'; 17 | 18 | /** 19 | * @dev ElasticGovernanceToken contract outlines and defines all the functionality 20 | * of an ElasticGovernanceToken and also serves as it's storage 21 | */ 22 | contract ElasticGovernanceToken is IElasticToken, ReentryProtection { 23 | address public burner; 24 | address public daoAddress; 25 | address public ecosystemModelAddress; 26 | address public minter; 27 | bool public initialized; 28 | 29 | mapping(address => mapping(address => uint256)) private _allowances; 30 | 31 | modifier onlyDAO() { 32 | require(msg.sender == daoAddress || msg.sender == minter, 'ElasticDAO: Not authorized'); 33 | _; 34 | } 35 | 36 | modifier onlyDAOorBurner() { 37 | require(msg.sender == daoAddress || msg.sender == burner, 'ElasticDAO: Not authorized'); 38 | _; 39 | } 40 | 41 | modifier onlyDAOorMinter() { 42 | require(msg.sender == daoAddress || msg.sender == minter, 'ElasticDAO: Not authorized'); 43 | _; 44 | } 45 | 46 | /** 47 | * @notice initializes the ElasticGovernanceToken 48 | * 49 | * @param _burner - the address which can burn tokens 50 | * @param _daoAddress - the address of the deployed ElasticDAO 51 | * @param _ecosystemModelAddress - the address of the ecosystem model 52 | * @param _minter - the address which can mint tokens 53 | * 54 | * @dev Requirements: 55 | * - The token should not already be initialized 56 | * - The address of the burner cannot be zero 57 | * - The address of the deployed ElasticDAO cannot be zero 58 | * - The address of the ecosystemModelAddress cannot be zero 59 | * - The address of the minter cannot be zero 60 | * 61 | * @return bool 62 | */ 63 | function initialize( 64 | address _burner, 65 | address _daoAddress, 66 | address _ecosystemModelAddress, 67 | address _minter 68 | ) external preventReentry returns (bool) { 69 | require(initialized == false, 'ElasticDAO: Already initialized'); 70 | require(_burner != address(0), 'ElasticDAO: Address Zero'); 71 | require(_daoAddress != address(0), 'ElasticDAO: Address Zero'); 72 | require(_ecosystemModelAddress != address(0), 'ElasticDAO: Address Zero'); 73 | require(_minter != address(0), 'ElasticDAO: Address Zero'); 74 | 75 | initialized = true; 76 | burner = _burner; 77 | daoAddress = _daoAddress; 78 | ecosystemModelAddress = _ecosystemModelAddress; 79 | minter = _minter; 80 | 81 | return true; 82 | } 83 | 84 | /** 85 | * @notice Returns the remaining number of tokens that @param _spender will be 86 | * allowed to spend on behalf of @param _owner through {transferFrom}. This is 87 | * zero by default 88 | * 89 | * @param _spender - the address of the spender 90 | * @param _owner - the address of the owner 91 | * 92 | * @dev This value changes when {approve} or {transferFrom} are called 93 | * 94 | * @return uint256 95 | */ 96 | function allowance(address _owner, address _spender) external view override returns (uint256) { 97 | return _allowances[_owner][_spender]; 98 | } 99 | 100 | /** 101 | * @notice Sets @param _amount as the allowance of @param _spender over the caller's tokens 102 | * 103 | * @param _spender - the address of the spender 104 | * 105 | * @dev 106 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 107 | * that someone may use both the old and the new allowance by unfortunate 108 | * transaction ordering. One possible solution to mitigate this race 109 | * condition is to first reduce the spender's allowance to 0 and set the 110 | * desired value afterwards: 111 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 112 | * 113 | * @dev Emits an {Approval} event 114 | * 115 | * @return bool 116 | */ 117 | function approve(address _spender, uint256 _amount) 118 | external 119 | override 120 | preventReentry 121 | returns (bool) 122 | { 123 | _approve(msg.sender, _spender, _amount); 124 | return true; 125 | } 126 | 127 | /** 128 | * @notice Returns the amount of tokens owned by @param _account using ElasticMath 129 | * 130 | * @param _account - address of the account 131 | * 132 | * @dev the number of tokens is given by: 133 | * t = lambda * m * k 134 | * 135 | * t - number of tokens 136 | * m - lambda modifier - it's value increases every time someone joins the DAO 137 | * k - constant token multiplier - it increases the number of tokens 138 | * that each member of the DAO has with respect to their lambda 139 | * 140 | * Further math and documentaion of 't' can be found at ../libraries/ElasticMath.sol 141 | * 142 | * @return uint256 143 | */ 144 | function balanceOf(address _account) external view override returns (uint256) { 145 | Token.Instance memory token = _getToken(); 146 | TokenHolder.Instance memory tokenHolder = _getTokenHolder(_account); 147 | uint256 t = ElasticMath.t(tokenHolder.lambda, token.k, token.m); 148 | 149 | return t; 150 | } 151 | 152 | /** 153 | * @notice Returns the amount of shares ( lambda ) owned by _account. 154 | * 155 | * @param _account - address of the account 156 | * 157 | * @return lambda uint256 - lambda is the number of shares 158 | */ 159 | function balanceOfInShares(address _account) external view override returns (uint256) { 160 | TokenHolder.Instance memory tokenHolder = _getTokenHolder(_account); 161 | return tokenHolder.lambda; 162 | } 163 | 164 | /** 165 | * @notice Returns the amount of tokens @param _account can vote with, using ElasticMath 166 | * 167 | * @param _account - the address of the account 168 | * 169 | * @dev checks if @param _account has more or less lambda than maxVotingLambda, 170 | * based on which number of tokens (t) @param _account can vote with is calculated. 171 | * Further math and documentaion of 't' can be found at ../libraries/ElasticMath.sol 172 | * 173 | * @return balance uint256 numberOfTokens (t) 174 | */ 175 | function balanceOfVoting(address _account) external view returns (uint256 balance) { 176 | Token.Instance memory token = _getToken(); 177 | TokenHolder.Instance memory tokenHolder = _getTokenHolder(_account); 178 | uint256 maxVotingLambda = _getDAO().maxVotingLambda; 179 | 180 | if (tokenHolder.lambda > maxVotingLambda) { 181 | return ElasticMath.t(maxVotingLambda, token.k, token.m); 182 | } else { 183 | return ElasticMath.t(tokenHolder.lambda, token.k, token.m); 184 | } 185 | } 186 | 187 | /** 188 | * @notice Reduces the balance(tokens) of @param _account by _amount 189 | * 190 | * @param _account address of the account 191 | * 192 | * @param _amount - the amount by which the number of tokens is to be reduced 193 | * 194 | * @return bool 195 | */ 196 | function burn(address _account, uint256 _amount) 197 | external 198 | override 199 | onlyDAOorBurner 200 | preventReentry 201 | returns (bool) 202 | { 203 | _burn(_account, _amount); 204 | return true; 205 | } 206 | 207 | /** 208 | * @notice Reduces the balance(lambda) of @param _account by _amount 209 | * 210 | * @param _account - address of the account 211 | * 212 | * @param _amount - the amount by which the number of shares has to be reduced 213 | * 214 | * @return bool 215 | */ 216 | function burnShares(address _account, uint256 _amount) 217 | external 218 | override 219 | onlyDAOorBurner 220 | preventReentry 221 | returns (bool) 222 | { 223 | _burnShares(_account, _amount); 224 | return true; 225 | } 226 | 227 | /** 228 | * @notice returns the number of decimals 229 | * 230 | * @return 18 231 | */ 232 | function decimals() external pure returns (uint256) { 233 | return 18; 234 | } 235 | 236 | /** 237 | * @notice decreases the allowance of @param _spender by _subtractedValue 238 | * 239 | * @param _spender - address of the spender 240 | * @param _subtractedValue - the value the allowance has to be decreased by 241 | * 242 | * @dev Requirement: 243 | * Allowance cannot be lower than 0 244 | * 245 | * @return bool 246 | */ 247 | function decreaseAllowance(address _spender, uint256 _subtractedValue) 248 | external 249 | preventReentry 250 | returns (bool) 251 | { 252 | uint256 newAllowance = SafeMath.sub(_allowances[msg.sender][_spender], _subtractedValue); 253 | 254 | require(newAllowance > 0, 'ElasticDAO: Allowance decrease less than 0'); 255 | 256 | _approve(msg.sender, _spender, newAllowance); 257 | return true; 258 | } 259 | 260 | /** 261 | * @notice increases the allowance of @param _spender by _addedValue 262 | * 263 | * @param _spender - address of the spender 264 | * @param _addedValue - the value the allowance has to be increased by 265 | * 266 | * @return bool 267 | */ 268 | function increaseAllowance(address _spender, uint256 _addedValue) 269 | external 270 | preventReentry 271 | returns (bool) 272 | { 273 | _approve(msg.sender, _spender, SafeMath.add(_allowances[msg.sender][_spender], _addedValue)); 274 | return true; 275 | } 276 | 277 | /** 278 | * @dev mints @param _amount tokens for @param _account 279 | * @param _amount - the amount of tokens to be minted 280 | * @param _account - the address of the account for whom the token have to be minted to 281 | * @return bool 282 | */ 283 | function mint(address _account, uint256 _amount) 284 | external 285 | onlyDAOorMinter 286 | preventReentry 287 | returns (bool) 288 | { 289 | _mint(_account, _amount); 290 | 291 | return true; 292 | } 293 | 294 | /** 295 | * @dev mints @param _amount of shares for @param _account 296 | * @param _account address of the account 297 | * @param _amount - the amount of shares to be minted 298 | * @return bool 299 | */ 300 | function mintShares(address _account, uint256 _amount) 301 | external 302 | override 303 | onlyDAOorMinter 304 | preventReentry 305 | returns (bool) 306 | { 307 | _mintShares(_account, _amount); 308 | return true; 309 | } 310 | 311 | /** 312 | * @dev Returns the name of the token. 313 | * @return string - name of the token 314 | */ 315 | function name() external view returns (string memory) { 316 | return _getToken().name; 317 | } 318 | 319 | /** 320 | * @notice Returns the number of token holders of ElasticGovernanceToken 321 | * 322 | * @return uint256 numberOfTokenHolders 323 | */ 324 | function numberOfTokenHolders() external view override returns (uint256) { 325 | return _getToken().numberOfTokenHolders; 326 | } 327 | 328 | /** 329 | * @notice sets the burner of the ElasticGovernanceToken 330 | * a Burner is an address that can burn tokens(reduce the amount of tokens in circulation) 331 | * 332 | * @param _burner - the address of the burner 333 | * 334 | * @dev Requirement: 335 | * - Address of the burner cannot be zero address 336 | * 337 | * @return bool 338 | */ 339 | function setBurner(address _burner) external onlyDAO preventReentry returns (bool) { 340 | require(_burner != address(0), 'ElasticDAO: Address Zero'); 341 | 342 | burner = _burner; 343 | 344 | return true; 345 | } 346 | 347 | /** 348 | * @notice sets the minter of the ElasticGovernanceToken 349 | * a Minter is an address that can mint tokens(increase the amount of tokens in circulation) 350 | * 351 | * @param _minter - address of the minter 352 | * 353 | * @dev Requirement: 354 | * - Address of the minter cannot be zero address 355 | * 356 | * @return bool 357 | */ 358 | function setMinter(address _minter) external onlyDAO preventReentry returns (bool) { 359 | require(_minter != address(0), 'ElasticDAO: Address Zero'); 360 | 361 | minter = _minter; 362 | 363 | return true; 364 | } 365 | 366 | /** 367 | * @dev Returns the symbol of the token, usually a shorter version of the 368 | * name. 369 | * 370 | * @return string - the symbol of the token 371 | */ 372 | function symbol() external view returns (string memory) { 373 | return _getToken().symbol; 374 | } 375 | 376 | /** 377 | * @notice returns the totalSupply of tokens in the DAO 378 | * 379 | * @dev 380 | * t - the total number of tokens in the DAO 381 | * lambda - the total number of shares outstanding in the DAO currently 382 | * m - current value of the share modifier 383 | * k - constant 384 | * t = ( lambda * m * k ) 385 | * Further math and documentaion of 't' can be found at ../libraries/ElasticMath.sol 386 | * 387 | * @return uint256 - the value of t 388 | */ 389 | function totalSupply() external view override returns (uint256) { 390 | Token.Instance memory token = _getToken(); 391 | return ElasticMath.t(token.lambda, token.k, token.m); 392 | } 393 | 394 | /** 395 | * @notice Returns the current lambda value 396 | * 397 | * @return uint256 lambda 398 | */ 399 | function totalSupplyInShares() external view override returns (uint256) { 400 | Token.Instance memory token = _getToken(); 401 | return token.lambda; 402 | } 403 | 404 | /** 405 | * @dev Moves @param _amount tokens from the caller's account to @param _to address 406 | * 407 | * Returns a boolean value indicating whether the operation succeeded 408 | * 409 | * Emits a {Transfer} event 410 | * @return bool 411 | */ 412 | function transfer(address _to, uint256 _amount) external override preventReentry returns (bool) { 413 | _transfer(msg.sender, _to, _amount); 414 | return true; 415 | } 416 | 417 | /** 418 | * @dev Moves @param _amount tokens from @param _from to @param _to using the 419 | * allowance mechanism. @param _amount is then deducted from the caller's 420 | * allowance 421 | * 422 | * Returns a boolean value indicating whether the operation succeeded 423 | * 424 | * Emits a {Transfer} event 425 | * @return bool 426 | */ 427 | function transferFrom( 428 | address _from, 429 | address _to, 430 | uint256 _amount 431 | ) external override preventReentry returns (bool) { 432 | require(msg.sender == _from || _amount <= _allowances[_from][msg.sender], 'ERC20: Bad Caller'); 433 | 434 | if (msg.sender != _from && _allowances[_from][msg.sender] != uint256(-1)) { 435 | _allowances[_from][msg.sender] = SafeMath.sub(_allowances[_from][msg.sender], _amount); 436 | emit Approval(msg.sender, _to, _allowances[_from][msg.sender]); 437 | } 438 | 439 | _transfer(_from, _to, _amount); 440 | return true; 441 | } 442 | 443 | // Private 444 | 445 | function _approve( 446 | address _owner, 447 | address _spender, 448 | uint256 _amount 449 | ) internal { 450 | require(_owner != address(0), 'ERC20: approve from the zero address'); 451 | require(_spender != address(0), 'ERC20: approve to the zero address'); 452 | 453 | _allowances[_owner][_spender] = _amount; 454 | 455 | emit Approval(_owner, _spender, _amount); 456 | } 457 | 458 | function _burn(address _account, uint256 _deltaT) internal { 459 | Token.Instance memory token = _getToken(); 460 | uint256 deltaLambda = ElasticMath.lambdaFromT(_deltaT, token.k, token.m); 461 | _burnShares(_account, deltaLambda); 462 | } 463 | 464 | function _burnShares(address _account, uint256 _deltaLambda) internal { 465 | Ecosystem.Instance memory ecosystem = _getEcosystem(); 466 | Token tokenStorage = Token(ecosystem.tokenModelAddress); 467 | Token.Instance memory token = tokenStorage.deserialize(address(this), ecosystem); 468 | TokenHolder.Instance memory tokenHolder = _getTokenHolder(_account); 469 | bool alreadyTokenHolder = tokenHolder.lambda > 0; 470 | 471 | tokenHolder = _updateBalance(tokenHolder, false, _deltaLambda); 472 | 473 | token.lambda = SafeMath.sub(token.lambda, _deltaLambda); 474 | tokenStorage.serialize(token); 475 | 476 | TokenHolder tokenHolderStorage = TokenHolder(ecosystem.tokenHolderModelAddress); 477 | tokenHolderStorage.serialize(tokenHolder); 478 | _updateNumberOfTokenHolders(alreadyTokenHolder, token, tokenHolder, tokenStorage); 479 | emit Transfer(_account, address(0), _deltaLambda); 480 | } 481 | 482 | function _mint(address _account, uint256 _deltaT) internal { 483 | Token.Instance memory token = _getToken(); 484 | uint256 deltaLambda = ElasticMath.lambdaFromT(_deltaT, token.k, token.m); 485 | _mintShares(_account, deltaLambda); 486 | } 487 | 488 | function _mintShares(address _account, uint256 _deltaLambda) internal { 489 | Ecosystem.Instance memory ecosystem = _getEcosystem(); 490 | Token tokenStorage = Token(ecosystem.tokenModelAddress); 491 | Token.Instance memory token = tokenStorage.deserialize(address(this), ecosystem); 492 | TokenHolder.Instance memory tokenHolder = _getTokenHolder(_account); 493 | bool alreadyTokenHolder = tokenHolder.lambda > 0; 494 | 495 | uint256 deltaT = ElasticMath.t(_deltaLambda, token.k, token.m); 496 | 497 | tokenHolder = _updateBalance(tokenHolder, true, _deltaLambda); 498 | 499 | token.lambda = SafeMath.add(token.lambda, _deltaLambda); 500 | tokenStorage.serialize(token); 501 | 502 | TokenHolder tokenHolderStorage = TokenHolder(ecosystem.tokenHolderModelAddress); 503 | tokenHolderStorage.serialize(tokenHolder); 504 | _updateNumberOfTokenHolders(alreadyTokenHolder, token, tokenHolder, tokenStorage); 505 | 506 | emit Transfer(address(0), _account, deltaT); 507 | } 508 | 509 | function _transfer( 510 | address _from, 511 | address _to, 512 | uint256 _deltaT 513 | ) internal { 514 | Ecosystem.Instance memory ecosystem = _getEcosystem(); 515 | Token tokenStorage = Token(ecosystem.tokenModelAddress); 516 | Token.Instance memory token = tokenStorage.deserialize(address(this), ecosystem); 517 | 518 | TokenHolder.Instance memory fromTokenHolder = _getTokenHolder(_from); 519 | TokenHolder.Instance memory toTokenHolder = _getTokenHolder(_to); 520 | bool fromAlreadyTokenHolder = fromTokenHolder.lambda > 0; 521 | bool toAlreadyTokenHolder = toTokenHolder.lambda > 0; 522 | 523 | uint256 deltaLambda = ElasticMath.lambdaFromT(_deltaT, token.k, token.m); 524 | uint256 deltaT = ElasticMath.t(deltaLambda, token.k, token.m); 525 | 526 | fromTokenHolder = _updateBalance(fromTokenHolder, false, deltaLambda); 527 | toTokenHolder = _updateBalance(toTokenHolder, true, deltaLambda); 528 | 529 | TokenHolder tokenHolderStorage = TokenHolder(ecosystem.tokenHolderModelAddress); 530 | tokenHolderStorage.serialize(fromTokenHolder); 531 | tokenHolderStorage.serialize(toTokenHolder); 532 | _updateNumberOfTokenHolders(fromAlreadyTokenHolder, token, fromTokenHolder, tokenStorage); 533 | _updateNumberOfTokenHolders(toAlreadyTokenHolder, token, toTokenHolder, tokenStorage); 534 | 535 | emit Transfer(_from, _to, deltaT); 536 | } 537 | 538 | function _updateBalance( 539 | TokenHolder.Instance memory _tokenHolder, 540 | bool _isIncreasing, 541 | uint256 _deltaLambda 542 | ) internal pure returns (TokenHolder.Instance memory) { 543 | if (_isIncreasing) { 544 | _tokenHolder.lambda = SafeMath.add(_tokenHolder.lambda, _deltaLambda); 545 | } else { 546 | _tokenHolder.lambda = SafeMath.sub(_tokenHolder.lambda, _deltaLambda); 547 | } 548 | 549 | return _tokenHolder; 550 | } 551 | 552 | function _updateNumberOfTokenHolders( 553 | bool alreadyTokenHolder, 554 | Token.Instance memory token, 555 | TokenHolder.Instance memory tokenHolder, 556 | Token tokenStorage 557 | ) internal { 558 | if (tokenHolder.lambda > 0 && alreadyTokenHolder == false) { 559 | tokenStorage.updateNumberOfTokenHolders(token, SafeMath.add(token.numberOfTokenHolders, 1)); 560 | } 561 | 562 | if (tokenHolder.lambda == 0 && alreadyTokenHolder) { 563 | tokenStorage.updateNumberOfTokenHolders(token, SafeMath.sub(token.numberOfTokenHolders, 1)); 564 | } 565 | } 566 | 567 | // Private Getters 568 | 569 | function _getDAO() internal view returns (DAO.Instance memory) { 570 | Ecosystem.Instance memory ecosystem = _getEcosystem(); 571 | return DAO(ecosystem.daoModelAddress).deserialize(daoAddress, ecosystem); 572 | } 573 | 574 | function _getEcosystem() internal view returns (Ecosystem.Instance memory) { 575 | return Ecosystem(ecosystemModelAddress).deserialize(daoAddress); 576 | } 577 | 578 | function _getTokenHolder(address _account) internal view returns (TokenHolder.Instance memory) { 579 | Ecosystem.Instance memory ecosystem = _getEcosystem(); 580 | return 581 | TokenHolder(ecosystem.tokenHolderModelAddress).deserialize( 582 | _account, 583 | ecosystem, 584 | Token(ecosystem.tokenModelAddress).deserialize(address(this), ecosystem) 585 | ); 586 | } 587 | 588 | function _getToken() internal view returns (Token.Instance memory) { 589 | Ecosystem.Instance memory ecosystem = _getEcosystem(); 590 | return Token(ecosystem.tokenModelAddress).deserialize(address(this), ecosystem); 591 | } 592 | } 593 | --------------------------------------------------------------------------------