├── .abi.js ├── .gitattributes ├── .gitignore ├── .solcover.js ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── contracts ├── Migrations.sol ├── core │ ├── AbstractStorage.sol │ ├── Contract.sol │ ├── Proxy.sol │ └── ScriptExec.sol ├── interfaces │ ├── GetterInterface.sol │ ├── RegistryInterface.sol │ └── StorageInterface.sol ├── lib │ ├── ArrayUtils.sol │ ├── SafeMath.sol │ └── StringUtils.sol └── registry │ ├── RegistryExec.sol │ ├── RegistryIdx.sol │ └── features │ └── Provider.sol ├── migrations └── 1_initial_migration.js ├── package-lock.json ├── package.json ├── test ├── abstract_storage_tests.js ├── fixtures │ └── scenarios │ │ └── .keep ├── mock │ ├── AppInitMock.sol │ ├── EmitsApp.sol │ ├── InvalidApp.sol │ ├── MixedApp.sol │ ├── PayableApp.sol │ ├── RevertApp.sol │ ├── RevertHelper.sol │ ├── StdApp.sol │ └── application │ │ └── functions │ │ ├── ApplicationMockFuncLib.sol │ │ └── init │ │ ├── ApplicationMockInit.sol │ │ ├── ApplicationMockNonDefaultInit.sol │ │ ├── MockAppOne.sol │ │ ├── MockAppThree.sol │ │ └── MockAppTwo.sol ├── registry │ └── RegistryExecMock.sol ├── registry_exec_tests.js ├── support │ ├── chai.js │ ├── evm.js │ └── utils.js └── util │ ├── AppInitUtil.sol │ ├── AppMockUtil.sol │ ├── TestUtils.sol │ ├── registry │ └── RegistryUtil.sol │ └── scriptExec │ └── AppMockUtilContext.sol ├── truffle-config.js └── truffle.js /.abi.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | 4 | const OUTPUT_DIR = process.env.OUTPUT || './build/abi/'; 5 | 6 | if (!fs.existsSync(OUTPUT_DIR)) { 7 | fs.mkdirSync(OUTPUT_DIR); 8 | } 9 | 10 | function readFiles(dirname, onFileContent, onError) { 11 | fs.readdir(dirname, function(err, filenames) { 12 | if (err) { 13 | onError(err); 14 | return; 15 | } 16 | filenames.forEach(function(filename) { 17 | fs.readFile(dirname + filename, 'utf-8', function(err, content) { 18 | if (err) { 19 | onError(err); 20 | return; 21 | } 22 | onFileContent(filename, content); 23 | }); 24 | }); 25 | }); 26 | } 27 | 28 | let dir = './build/contracts/'; 29 | readFiles(dir, function(filename, content) { 30 | if (filename.includes(".abi.")) return; 31 | let abi = JSON.stringify(JSON.parse(content).abi, null, 2); 32 | fs.writeFileSync(`${OUTPUT_DIR}${path.basename(filename, '.json')}.abi.json`, abi); 33 | }, function(err) { 34 | throw err; 35 | }); 36 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | flat/ 3 | mochawesome-report/ 4 | mochawesome-reports/ 5 | node_modules/ 6 | tmp/ 7 | 8 | npm-debug.log 9 | 10 | package.json # using npm-shrinkwrap to pin truffle's solc version 11 | 12 | convert_bytes.py* 13 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | dir: './', 3 | norpc: true, 4 | testCommand: './node_modules/.bin/truffle test', 5 | skipFiles: ['Migrations.sol'] //, 'lib/*'], 6 | } 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | install: 5 | - "npm install" 6 | test: 7 | -"npm test" 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Authio Inc., Provide Technologies Inc. and other contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: abi clean compile flat test coverage 2 | 3 | abi: clean compile 4 | node ./.abi.js 5 | 6 | clean: 7 | rm -rf build/ 8 | rm -rf flat/ 9 | rm -rf tmp/ 10 | 11 | compile: 12 | node_modules/.bin/truffle compile 13 | 14 | coverage: 15 | node_modules/.bin/solidity-coverage 16 | 17 | flat: 18 | rm -rf tmp/ 19 | mkdir tmp 20 | mkdir -p flat 21 | 22 | cp contracts/*.sol tmp/ 23 | cp contracts/interfaces/* tmp/ 24 | cp contracts/lib/* tmp/ 25 | cp contracts/registry/features/*.sol tmp/ 26 | cp contracts/registry/*.sol tmp/ 27 | cp contracts/core/* tmp/ 28 | 29 | rm tmp/Migrations.sol 30 | 31 | sed -i '' -e "s/\(import \)\(.*\)\/\(.*\).sol/import '.\/\3.sol/g" tmp/* 32 | node_modules/.bin/truffle-flattener tmp/* | sed "1s/.*/pragma solidity ^0.4.23;/" > flat/auth-os.sol 33 | 34 | test: 35 | node_modules/.bin/truffle test 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![auth-os](https://uploads-ssl.webflow.com/59fdc220cd54e70001a36846/5ac504590012b0bc9d789ab8_auth_os%20Logo%20Blue.png) 2 | 3 | # auth-os core: 4 | 5 | This package contains a set of contracts, libraries, and interfaces used in the development of auth-os applications. auth-os utilizes a unique application architecture to facilitate the building of contracts that are truly modular, as well as interoperable. 6 | 7 | ### Install: 8 | 9 | `npm install authos-solidity` 10 | 11 | ### Deployment Process: 12 | 13 | Deployment of an auth-os application begins with the storage contract and script registry application. Deploy the following files: `AbstractStorage.sol`, `RegistryIdx.sol`, `Provider.sol`, `RegistryExec.sol`. Use `RegistryExec.configure` to configure the registry executor to use the deployed storage contract, as well as provide an interface for a provider address to register applications within the registry app. The provider and exec admin addresses will be able to register apps. 14 | 15 | Once the above core files are deployed, deploy the files for an application, including its index file (contains an init function as well as several getters). Deployed applications can be registered in the RegistryExec contract by calling `RegistryExec.registerApp`, and passing in the name of the application (hex), the app's index address, all of the function selectors the app exposes, and the corresponding addresses that expose those selectors. 16 | 17 | Apps deployed in this manner are now able to be initialized and used by anyone - through a `Proxy` file, or through the `RegistryExec` file itself. 18 | 19 | Several of these contracts have already been deployed on Ropsten, as well as the mainnet - 20 | 21 | #### Ropsten: 22 | 23 | `AbstractStorage`: https://ropsten.etherscan.io/address/0x7815b0f22c1444ad56e6d207c1c58e8b8bd17931#code 24 | 25 | `Provider`: https://ropsten.etherscan.io/address/0xfbfb8f79ca2c6c7af3c6a38d1c2feacefd140b07#code 26 | 27 | `RegistryIdx`: https://ropsten.etherscan.io/address/0x2e6e81016eb1e63d972133d823a6759a6e2737b5#code 28 | 29 | `RegistryExec`: https://ropsten.etherscan.io/address/0xf1abf2cfe9cecf189fdab4e26e8da6ed587f11d1#code 30 | 31 | 32 | #### Mainnet: 33 | 34 | `AbstractStorage`: https://etherscan.io/address/0xb1d914e7c55f16c2bacac11af6b3e011aee38caf#code 35 | 36 | `Provider`: https://etherscan.io/address/0x9ca096ea086e6c27d3b69d1f8ba4278502815fa1#code 37 | 38 | `RegistryIdx`: https://etherscan.io/address/0xbc25d8c026ef18ca00bfa328a2c03c54af3c3e95#code 39 | 40 | `RegistryExec`: https://etherscan.io/address/0xa609f05557d458727c90603adad436041915a0ca#code 41 | 42 | 43 | ### Using auth-os applications: 44 | 45 | When using auth_os applications, it is important to note that for each transaction made within auth_os, at least 1 `revert` instruction will be executed. When using sites like etherscan, it may appear that several calls are failing - the differentiator between a successful and failed call is whether or not `AbstractStorage` reverted. In this case, `ScriptExec` and variants will emit a `StorageException` event with a message describing the error that occured. In the event of successful execution, `AbstractStorage` will emit an `ApplicationExecution` event. 46 | 47 | ### About: 48 | 49 | auth_os combines a traditional 'upgrade by proxy' architecture with a new method for defining storage locations within an application - 'abstract storage'. This combination allows applications to be completely upgradable: application logic, storage targets for data, and data types can all be upgraded without fear of overwriting previously-defined values. This is accomplished in abstract storage through the use of relative storage locations, which emulate Solidity mappings save for the fact that these locations are able to define their own 'starting seed' for storage. 50 | 51 | For example, defining a state variable creates, for that contract, a fixed point referencing a location in storage: 52 | ```Solidity 53 | contract A { 54 | uint public a; // Will always reference storage slot 0 55 | uint public b; // Will always reference storage slot 1 56 | } 57 | ``` 58 | The drawback of this is that changing the size of the data stored in `a` in a consecutive version will likely overwrite the value stored at `b`. In order to avoid this limitation, auth_os applications declare their data fields to be located at some hash, which is fed some unique, identifying seed. Any files referencing that seed will be able to read and interact with the same data as other files, though they may choose to interpret it in a different way. As an example: 59 | ```Solidity 60 | // DO NOT USE IN PRODUCTION 61 | library B { 62 | bytes32 constant NUM_OWNERS = keccak256("owner_list"); 63 | 64 | // Get the number of owners` 65 | function getNumOwners() public view returns (uint num) { 66 | bytes32 owners = NUM_OWNERS; 67 | assembly { num := sload(owners) } 68 | } 69 | } 70 | 71 | library C { 72 | bytes32 constant OWNER_LIST = keccak256("owner_list"); 73 | 74 | // Returns the location of the list index 75 | function ownerAt(uint _idx) internal pure returns (bytes32) 76 | { return bytes32(32 + (32 * _idx) + uint(OWNER_LIST)); } 77 | 78 | // Get the list of owners 79 | function getOwners() public view returns (address[] owners) { 80 | uint len; 81 | bytes32 list = OWNER_LIST; 82 | assembly { len := sload(list) } 83 | owners = new address[](len); 84 | for (uint i = 0; i < owners.length; i++) { 85 | bytes32 loc = ownerAt(i); 86 | address owner; 87 | assembly { owner := sload(loc) } 88 | owners[i] = owner; 89 | } 90 | } 91 | } 92 | ``` 93 | In the above files, `B.getNumOwners()` will interpret the value stored at `NUM_OWNERS` as a simple `uint`. This is in contrast to `C.getOwners()`, which interprets the value stored there as a list, and returns the entire list to the caller. This concept allows for the implementation of complex types not supported in standard Solidity - the only prerequisite is to build the functions that interpret these locations in storage correctly. As a result of using this structure, applications can be sure that any upgrade they make will be overwrite-safe - as the hashed locations will take care of any potential overlaps between an applications fields. 94 | 95 | Extending this concept, it is possible to implement a protocol through which unrelated applications can store their data in the same contract (same address) while still being able to deterministically read from these locations, as well as direct storage to write to these locations. Plainly, if abstract storage assigns a unique `id` to each instance of each application created within itself, we know that if storage location hashing is able to be enforced, applications can share the same storage contract without the risk of malicious (or unintentional) data overwrites. 96 | 97 | Enforcing this behavior is simple - the basic premise is that the application, following execution, will return a formatted request to storage, which will ensure that each location to which data must be stored is first hashed with the application's unique `execution_id`. What is not so simple is: allowing for this open instantiation of applications within storage and enforcing this behavior, while remaining fairly efficient. Applications can be instantiated by anyone - and as a result must be treated with the utmost caution. Applications may attempt to overwrite data stored in other applications: it is imperative that the storage contract have safeguards in place to ensure that this is not possible. 98 | 99 | The safeguards set in place depend primarily on the method of 'running' these instantiated applications. Initially, the storage contract used a `staticcall` to call the target application, while ensuring that no state change would occur as a result of running this external code. While this method works very well to ensure that executed applications are unable to call back into storage, or change the state of other apps, there is an unfortunate drawback in efficiency. Because `staticcall` does not use the calling contract's context, the executed application cannot read directly from storage and must rely on expensive external calls to read from storage. `AbstractStorage` exposes two functions for this - `read` and `readMulti`, which hash a location (or locations) with the passed-in `execution_id`, read the resulting data from storage, and return to the calling contract. Upon completion of execution, an application should have some list of storage locations along with data to commit to those locations. Instead of simply storing this data locally (not possible, as the app is a library and cannot change its state), the application `return`s a formatted request to storage, which parses and *safely* executes the instructions contained in this request. The parser is still being used, and its current current implementation can be found here: https://github.com/auth-os/core/blob/dev/contracts/core/AbstractStorage.sol#L174. 100 | 101 | The obvious downside of these applications is the quickly-building cost of reading large amounts of data from storage. The implementing code required building buffers in runtime memory, which would be formatted to correctly request `read`s from storage. This, too, is a downside, as it requires building via a library that implements memory buffers - which is neither clean, nor simple to use. 102 | 103 | Instead of using `staticcall` to execute applications, it would be much more efficient to use `delegatecall`. `delegatecall` allows external code to be executed in the context of the calling contract. In essence, executed applications would be able to read from state locally, without the overhead of an external call. While this operation drastically improves the efficiency of these applications, `delegatecall` poses its own risks. A contract called with `delegatecall` has near-complete autonomy over the calling contract's state. It can `sstore` to arbitrary locations and execute external code with unexpected effects. For example, a `delegatecall`ed application could execute the `selfdestruct` opcode, destroying the storage contract and removing the accumulated state of all of its hosted app instances. Clearly, `delegatecall` is dangerous - but if we could enforce a method by which a `delegatecall`ed application could not affect state, the efficiency increase would make this implementation a clear winner. 104 | 105 | As it turns out, the same way that previously-described `staticcall`ed applications would return requests to store (and perform other actions) to storage following execution, a `delegatecall`ed application can incorporate the same mechanism by simply `revert`ing the same request to storage. `revert` can return data in exactly the same way `return` can - with the added effect that any state changes that took place during the call's execution, are reversed/removed. To add to this, the calling contract (`AbstractStorage`) can verify that this revert takes place - `delegatecall` will push a `0` (`false`) to the stack in the event that the call failed, and a `1` (`true`) on success. If storage sees that an application did not `revert` following execution, it is then able to `revert`, itself - ensuring that no unexpected state changes took place. If the storage contract observes a `revert` from its executed application, it can be sure that no malicious state change occured, and safely parse and execute its returned data. 106 | 107 | ### Benefits of upgrade by proxy + abstract storage + forced-revert delegatecall: 108 | 109 | 1. Applications should be able to be created in a way that makes re-using code not only trivial, but core to the implementation of the platform. Developers and users should have access to widely-used, templated contracts which can be simply, safely extended (without regard for changes in storage footprint). 110 | 2. Applications are built on a framework that is inherently receptive to upgradability - whether the application defines its own implementation of an upgrade protocol, or delegates this responsibility to some DAO or other authoritative body, upgradability itself should not be limited by types, storage footprint, locations, or anything else. 111 | 3. There is potential for serious interoperability between applications. Applications share the same storage contract - enabling other applications to directly view their data (with some pre-requisite knowledge of some interface or storage footprint). Before, this would require not only that the 'read target' define an explicit `get` function for the data being accessed, but also the gas overhead of an external call. Eliminating these requirements allows applications to read data stored by other applications in a vastly-more efficient and effective manner than before. 112 | - It is interesting to note that combining the `execution_id`s of two or more applications results in a set of locations that can be stored to, that is unique to that combination of `execution_id`s. Using an XOR, this combination becomes commutative and associative (`a^b == b^a` and `a^(b^c) == (a^b)^c`) - meaning that it should be fairly straightforward for applications to come to some agreement about the locations and protocols governing these shared storage locations. It may be possible for applications to implement their own versions of inheritance within storage, whereby applications can instantiate a set of 'child' applications which all share some set of locations in storage, and where the protocol for reading/writing to these locations (i.e. the protocol for inter-application-communication) would be defined and enforced by the parent application. 113 | - One interesting potential use-case is the creation of application-specific DAOs, where a set of similar applications would form their own 'DAO'. For example, perhaps every `ERC20` application together formed a DAO through which the `ERC20` standard itself could be upgraded or changed - then automatically carry out this upgrade, all without the pain of various 'custom' implementations lagging behind or not being supported. 114 | 115 | ### Downsides: 116 | 117 | 1. Currently, applications are not quite as readable as standard Solidity: https://github.com/auth-os/core/blob/dev/contracts/registry/features/Provider.sol#L55, as they must define and push to runtime memory buffers which hold formatted requests that will be parsed by storage (buffers were removed when reading data, but still exist when needing to `revert` data back to storage). 118 | - They are also not as easy to write. Developers would do well to keep a careful eye on the order in which these buffers are added to - allocating memory unexpectedly (for example, declaring or returning a `struct`) could likely result in the allocated memory being overwritten as the buffer expands. Currently, there are a few basic checks in place to ensure that execution follows at least a very basic standard pattern, but this will need to be improved upon significantly if the system is to be usable by most developers. 119 | - Some of the problems with readability lie with Solidity itself: lack of truly-usable memory and storage pointer types, as well as no real model for library inheritance, and no real 'generic' types means that in order to abstract auth_os' application implementations to a level where readability, writeability, and auditability find a happy balance, helper libraries chock full of assembly and low-level compiler manipulation must be incorporated (`Contract.sol`). Many of the aforementioned features are being actively worked on, but the current solutions for these problems are lacking. 120 | 2. `AbstractStorage` could be a single point of failure for several applications. While this is a very valid concern, this is exacerbated in a large amount by the current implementation of `AbstractStorage`. It incorporates an overly-complicated `bytes` parser which handles an application's output. While the current implementation is a large step up from the previous implementation (the last change abstracted a large portion of `AbstractStorage` and now allows applications to define many of these checks, requirements, and functions for themselves. This is a small step, but a step in the right direction - the implementation of `AbstractStorage` really deserves to be defined in hand-written bytecode - for maximum efficiency, and minimum complexity. Restricting functionality to a very small set of actions and treating `AbstractStorage` more as general I/O would hopefully simplify the implementation enough to be sure of security (especially with review from many developers). 121 | - Still, the idea of a single point of failure is a large one to simply dismiss. Further work will be required to narrow down the functionality of `AbstractStorage` enough to consider 122 | 123 | ### Conclusion: 124 | 125 | The combination of abstract storage, upgrade-by-proxy, and forced-revert delegatecall has the potential to define the kernel for a wide variety of truly modular applications. Applications built using this framework have the potential to be the most interoperable, extensible, and upgradable applications currently being built. There is still much to be learned as far as specific use-cases for this unique structure, but the potential it affords is too large to ignore. 126 | 127 | With further development of Solidity, further protocol upgrades, and further second-layer solutions put in place, I believe that a version of this framework could serve as a cornerstone upon which many other upgradable, extensible, and inter-operable applications are built. 128 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | modifier restricted() { 8 | if (msg.sender == owner) _; 9 | } 10 | 11 | constructor() public { 12 | owner = msg.sender; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/core/AbstractStorage.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | import '../interfaces/RegistryInterface.sol'; 4 | 5 | contract AbstractStorage { 6 | 7 | // Special storage locations - applications can read from 0x0 to get the execution id, and 0x20 8 | // to get the sender from which the call originated 9 | bytes32 private exec_id; 10 | address private sender; 11 | 12 | // Keeps track of the number of applicaions initialized, so that each application has a unique execution id 13 | uint private nonce; 14 | 15 | /// EVENTS /// 16 | 17 | event ApplicationInitialized(bytes32 indexed execution_id, address indexed index, address script_exec); 18 | event ApplicationExecution(bytes32 indexed execution_id, address indexed script_target); 19 | event DeliveredPayment(bytes32 indexed execution_id, address indexed destination, uint amount); 20 | 21 | /// CONSTANTS /// 22 | 23 | // STORAGE LOCATIONS // 24 | 25 | bytes32 internal constant EXEC_PERMISSIONS = keccak256('script_exec_permissions'); 26 | bytes32 internal constant APP_IDX_ADDR = keccak256('index'); 27 | 28 | // ACTION REQUESTORS // 29 | 30 | bytes4 internal constant EMITS = bytes4(keccak256('Emit((bytes32[],bytes)[])')); 31 | bytes4 internal constant STORES = bytes4(keccak256('Store(bytes32[])')); 32 | bytes4 internal constant PAYS = bytes4(keccak256('Pay(bytes32[])')); 33 | bytes4 internal constant THROWS = bytes4(keccak256('Error(string)')); 34 | 35 | // SELECTORS // 36 | 37 | bytes4 internal constant REG_APP 38 | = bytes4(keccak256('registerApp(bytes32,address,bytes4[],address[])')); 39 | bytes4 internal constant REG_APP_VER 40 | = bytes4(keccak256('registerAppVersion(bytes32,bytes32,address,bytes4[],address[])')); 41 | bytes4 internal constant UPDATE_EXEC_SEL 42 | = bytes4(keccak256('updateExec(address)')); 43 | bytes4 internal constant UPDATE_INST_SEL 44 | = bytes4(keccak256('updateInstance(bytes32,bytes32,bytes32)')); 45 | 46 | // Creates an instance of a registry application and returns the execution id 47 | function createRegistry(address _registry_idx, address _implementation) external returns (bytes32) { 48 | bytes32 new_exec_id = keccak256(++nonce); 49 | put(new_exec_id, keccak256(msg.sender, EXEC_PERMISSIONS), bytes32(1)); 50 | put(new_exec_id, APP_IDX_ADDR, bytes32(_registry_idx)); 51 | put(new_exec_id, keccak256(REG_APP, 'implementation'), bytes32(_implementation)); 52 | put(new_exec_id, keccak256(REG_APP_VER, 'implementation'), bytes32(_implementation)); 53 | put(new_exec_id, keccak256(UPDATE_INST_SEL, 'implementation'), bytes32(_implementation)); 54 | put(new_exec_id, keccak256(UPDATE_EXEC_SEL, 'implementation'), bytes32(_implementation)); 55 | emit ApplicationInitialized(new_exec_id, _registry_idx, msg.sender); 56 | return new_exec_id; 57 | } 58 | 59 | /// APPLICATION INSTANCE INITIALIZATION /// 60 | 61 | /* 62 | Executes an initialization function of an application, generating a new exec id that will be associated with that address 63 | 64 | @param _sender: The sender of the transaction, as reported by the script exec contract 65 | @param _app_name: The name of the application which will be instantiated 66 | @param _provider: The provider under which the application is registered 67 | @param _registry_id: The execution id of the registry app 68 | @param _calldata: The calldata to forward to the application 69 | @return new_exec_id: A new, unique execution id paired with the created instance of the application 70 | @return version: The name of the version of the instance 71 | */ 72 | function createInstance(address _sender, bytes32 _app_name, address _provider, bytes32 _registry_id, bytes _calldata) external payable returns (bytes32 new_exec_id, bytes32 version) { 73 | // Ensure valid input - 74 | require(_sender != 0 && _app_name != 0 && _provider != 0 && _registry_id != 0 && _calldata.length >= 4, 'invalid input'); 75 | 76 | // Create new exec id by incrementing the nonce - 77 | new_exec_id = keccak256(++nonce); 78 | 79 | // Sanity check - verify that this exec id is not linked to an existing application - 80 | assert(getIndex(new_exec_id) == address(0)); 81 | 82 | // Set the allowed addresses and selectors for the new instance, from the script registry - 83 | address index; 84 | (index, version) = setImplementation(new_exec_id, _app_name, _provider, _registry_id); 85 | 86 | // Set the exec id and sender addresses for the target application - 87 | setContext(new_exec_id, _sender); 88 | 89 | // Execute application, create a new exec id, and commit the returned data to storage - 90 | require(address(index).delegatecall(_calldata) == false, 'Unsafe execution'); 91 | // Get data returned from call revert and perform requested actions - 92 | executeAppReturn(new_exec_id); 93 | 94 | // Emit event 95 | emit ApplicationInitialized(new_exec_id, index, msg.sender); 96 | 97 | // If execution reaches this point, newly generated exec id should be valid - 98 | assert(new_exec_id != bytes32(0)); 99 | 100 | // Ensure that any additional balance is transferred back to the sender - 101 | if (address(this).balance > 0) 102 | address(msg.sender).transfer(address(this).balance); 103 | } 104 | 105 | /* 106 | Executes an initialized application associated with the given exec id, under the sender's address and with 107 | the given calldata 108 | 109 | @param _sender: The address reported as the call sender by the script exec contract 110 | @param _exec_id: The execution id corresponding to an instance of the application 111 | @param _calldata: The calldata to forward to the application 112 | @return n_emitted: The number of events emitted on behalf of the application 113 | @return n_paid: The number of destinations ETH was forwarded to on behalf of the application 114 | @return n_stored: The number of storage slots written to on behalf of the application 115 | */ 116 | function exec(address _sender, bytes32 _exec_id, bytes _calldata) external payable returns (uint n_emitted, uint n_paid, uint n_stored) { 117 | // Ensure valid input and input size - minimum 4 bytes 118 | require(_calldata.length >= 4 && _sender != address(0) && _exec_id != bytes32(0)); 119 | 120 | // Get the target address associated with the given exec id 121 | address target = getTarget(_exec_id, getSelector(_calldata)); 122 | require(target != address(0), 'Uninitialized application'); 123 | 124 | // Set the exec id and sender addresses for the target application - 125 | setContext(_exec_id, _sender); 126 | 127 | // Execute application and commit returned data to storage - 128 | require(address(target).delegatecall(_calldata) == false, 'Unsafe execution'); 129 | (n_emitted, n_paid, n_stored) = executeAppReturn(_exec_id); 130 | 131 | // If no events were emitted, no wei was forwarded, and no storage was changed, revert - 132 | if (n_emitted == 0 && n_paid == 0 && n_stored == 0) 133 | revert('No state change occured'); 134 | 135 | // Emit event - 136 | emit ApplicationExecution(_exec_id, target); 137 | 138 | // Ensure that any additional balance is transferred back to the sender - 139 | if (address(this).balance > 0) 140 | address(msg.sender).transfer(address(this).balance); 141 | } 142 | 143 | /// APPLICATION RETURNDATA HANDLING /// 144 | 145 | /* 146 | This function parses data returned by an application and executes requested actions. Because applications 147 | are assumed to be stateless, they cannot emit events, store data, or forward payment. Therefore, these 148 | steps to execution are handled in the storage contract by this function. 149 | 150 | Returned data can execute several actions requested by the application through the use of an 'action requestor': 151 | Some actions mirror nested dynamic return types, which are manually encoded and decoded as they are not supported 152 | 1. THROWS - App requests storage revert with a given message 153 | --Format: bytes 154 | --Payload is simply an array of bytes that will be reverted back to the caller 155 | 2. EMITS - App requests that events be emitted. Can provide topics to index, as well as arbitrary length data 156 | --Format: Event[] 157 | --Event format: [uint n_topics][bytes32 topic_0]...[bytes32 topic_n][uint data.length][bytes data] 158 | 3. STORES - App requests that data be stored to its storage. App storage locations are hashed with the app's exec id 159 | --Format: bytes32[] 160 | --bytes32[] consists of a data location followed by a value to place at that location 161 | --as such, its length must be even 162 | --Ex: [value_0][location_0]...[value_n][location_n] 163 | 4. PAYS - App requests that ETH sent to the contract be forwarded to other addresses. 164 | --Format: bytes32[] 165 | --bytes32[] consists of an address to send ETH to, followed by an amount to send to that address 166 | --As such, its length must be even 167 | --Ex: [amt_0][bytes32(destination_0)]...[amt_n][bytes32(destination_n)] 168 | 169 | Returndata is structured as an array of bytes, beginning with an action requestor ('THROWS', 'PAYS', etc) 170 | followed by that action's appropriately-formatted data (see above). Up to 3 actions with formatted data can be placed 171 | into returndata, and each must be unique (i.e. no two 'EMITS' actions). 172 | 173 | If the THROWS action is requested, it must be the first event requested. The event will be parsed 174 | and logged, and no other actions will be executed. If the THROWS requestor is not the first action 175 | requested, this function will throw 176 | 177 | @param _exec_id: The execution id which references this application's storage 178 | @return n_emitted: The number of events emitted on behalf of the application 179 | @return n_paid: The number of destinations ETH was forwarded to on behalf of the application 180 | @return n_stored: The number of storage slots written to on behalf of the application 181 | */ 182 | function executeAppReturn(bytes32 _exec_id) internal returns (uint n_emitted, uint n_paid, uint n_stored) { 183 | uint _ptr; // Will be a pointer to the data returned by the application call 184 | uint ptr_bound; // Will be the maximum value of the pointer possible (end of the memory stored in the pointer) 185 | (ptr_bound, _ptr) = getReturnedData(); 186 | // If the application reverted with an error, we can check directly for its selector - 187 | if (getAction(_ptr) == THROWS) { 188 | // Execute THROWS request 189 | doThrow(_ptr); 190 | // doThrow should revert, so we should never reach this point 191 | assert(false); 192 | } 193 | 194 | // Ensure there are at least 64 bytes stored at the pointer 195 | require(ptr_bound >= _ptr + 64, 'Malformed returndata - invalid size'); 196 | _ptr += 64; 197 | 198 | // Iterate over returned data and execute actions 199 | bytes4 action; 200 | while (_ptr <= ptr_bound && (action = getAction(_ptr)) != 0x0) { 201 | if (action == EMITS) { 202 | // If the action is EMITS, and this action has already been executed, throw 203 | require(n_emitted == 0, 'Duplicate action: EMITS'); 204 | // Otherwise, emit events and get amount of events emitted 205 | // doEmit returns the pointer incremented to the end of the data portion of the action executed 206 | (_ptr, n_emitted) = doEmit(_ptr, ptr_bound); 207 | // If 0 events were emitted, returndata is malformed: throw 208 | require(n_emitted != 0, 'Unfulfilled action: EMITS'); 209 | } else if (action == STORES) { 210 | // If the action is STORES, and this action has already been executed, throw 211 | require(n_stored == 0, 'Duplicate action: STORES'); 212 | // Otherwise, store data and get amount of slots written to 213 | // doStore increments the pointer to the end of the data portion of the action executed 214 | (_ptr, n_stored) = doStore(_ptr, ptr_bound, _exec_id); 215 | // If no storage was performed, returndata is malformed: throw 216 | require(n_stored != 0, 'Unfulfilled action: STORES'); 217 | } else if (action == PAYS) { 218 | // If the action is PAYS, and this action has already been executed, throw 219 | require(n_paid == 0, 'Duplicate action: PAYS'); 220 | // Otherwise, forward ETH and get amount of addresses forwarded to 221 | // doPay increments the pointer to the end of the data portion of the action executed 222 | (_ptr, n_paid) = doPay(_exec_id, _ptr, ptr_bound); 223 | // If no destinations recieved ETH, returndata is malformed: throw 224 | require(n_paid != 0, 'Unfulfilled action: PAYS'); 225 | } else { 226 | // Unrecognized action requested. returndata is malformed: throw 227 | revert('Malformed returndata - unknown action'); 228 | } 229 | } 230 | assert(n_emitted != 0 || n_paid != 0 || n_stored != 0); 231 | } 232 | 233 | /// HELPERS /// 234 | 235 | /* 236 | Reads application information from the script registry, and sets up permissions for the new instance's various functions 237 | 238 | @param _new_exec_id: The execution id being created, for which permissions will be registered 239 | @param _app_name: The name of the new application instance - corresponds to an application registered by the provider under that name 240 | @param _provider: The address of the account that registered an application under the given name 241 | @param _registry_id: The exec id of the registry from which the information will be read 242 | */ 243 | function setImplementation(bytes32 _new_exec_id, bytes32 _app_name, address _provider, bytes32 _registry_id) internal returns (address index, bytes32 version) { 244 | // Get the index address for the registry app associated with the passed-in exec id 245 | index = getIndex(_registry_id); 246 | require(index != address(0) && index != address(this), 'Registry application not found'); 247 | // Get the name of the latest version from the registry app at the given address 248 | version = RegistryInterface(index).getLatestVersion( 249 | address(this), _registry_id, _provider, _app_name 250 | ); 251 | // Ensure the version name is valid - 252 | require(version != bytes32(0), 'Invalid version name'); 253 | 254 | // Get the allowed selectors and addresses for the new instance from the registry app 255 | bytes4[] memory selectors; 256 | address[] memory implementations; 257 | (index, selectors, implementations) = RegistryInterface(index).getVersionImplementation( 258 | address(this), _registry_id, _provider, _app_name, version 259 | ); 260 | // Ensure a valid index address for the new instance - 261 | require(index != address(0), 'Invalid index address'); 262 | // Ensure a nonzero number of allowed selectors and implementing addresses - 263 | require(selectors.length == implementations.length && selectors.length != 0, 'Invalid implementation length'); 264 | 265 | // Set the index address for the new instance - 266 | bytes32 seed = APP_IDX_ADDR; 267 | put(_new_exec_id, seed, bytes32(index)); 268 | // Loop over implementing addresses, and map each function selector to its corresponding address for the new instance 269 | for (uint i = 0; i < selectors.length; i++) { 270 | require(selectors[i] != 0 && implementations[i] != 0, 'invalid input - expected nonzero implementation'); 271 | seed = keccak256(selectors[i], 'implementation'); 272 | put(_new_exec_id, seed, bytes32(implementations[i])); 273 | } 274 | 275 | return (index, version); 276 | } 277 | 278 | // Returns the index address of an application using a given exec id, or 0x0 279 | // if the instance does not exist 280 | function getIndex(bytes32 _exec_id) public view returns (address) { 281 | bytes32 seed = APP_IDX_ADDR; 282 | function (bytes32, bytes32) view returns (address) getter; 283 | assembly { getter := readMap } 284 | return getter(_exec_id, seed); 285 | } 286 | 287 | // Returns the address to which calldata with the given selector will be routed 288 | function getTarget(bytes32 _exec_id, bytes4 _selector) public view returns (address) { 289 | bytes32 seed = keccak256(_selector, 'implementation'); 290 | function (bytes32, bytes32) view returns (address) getter; 291 | assembly { getter := readMap } 292 | return getter(_exec_id, seed); 293 | } 294 | 295 | struct Map { mapping(bytes32 => bytes32) inner; } 296 | 297 | // Receives a storage pointer and returns the value mapped to the seed at that pointer 298 | function readMap(Map storage _map, bytes32 _seed) internal view returns (bytes32) { 299 | return _map.inner[_seed]; 300 | } 301 | 302 | // Maps the seed to the value within the execution id's storage 303 | function put(bytes32 _exec_id, bytes32 _seed, bytes32 _val) internal { 304 | function (bytes32, bytes32, bytes32) puts; 305 | assembly { puts := putMap } 306 | puts(_exec_id, _seed, _val); 307 | } 308 | 309 | // Receives a storage pointer and maps the seed to the value at that pointer 310 | function putMap(Map storage _map, bytes32 _seed, bytes32 _val) internal { 311 | _map.inner[_seed] = _val; 312 | } 313 | 314 | /// APPLICATION EXECUTION /// 315 | 316 | function getSelector(bytes memory _calldata) internal pure returns (bytes4 sel) { 317 | assembly { 318 | sel := and( 319 | mload(add(0x20, _calldata)), 320 | 0xffffffff00000000000000000000000000000000000000000000000000000000 321 | ) 322 | } 323 | } 324 | 325 | /* 326 | After validating that returned data is larger than 32 bytes, returns a pointer to the returned data 327 | in memory, as well as a pointer to the end of returndata in memory 328 | 329 | @return ptr_bounds: The pointer cannot be this value and be reading from returndata 330 | @return _returndata_ptr: A pointer to the returned data in memory 331 | */ 332 | function getReturnedData() internal pure returns (uint ptr_bounds, uint _returndata_ptr) { 333 | assembly { 334 | // returndatasize must be minimum 96 bytes (offset, length, and requestor) 335 | if lt(returndatasize, 0x60) { 336 | mstore(0, 0x20) 337 | mstore(0x20, 24) 338 | mstore(0x40, 'Insufficient return size') 339 | revert(0, 0x60) 340 | } 341 | // Get memory location to which returndata will be copied 342 | _returndata_ptr := msize 343 | // Copy returned data to pointer location 344 | returndatacopy(_returndata_ptr, 0, returndatasize) 345 | // Get maximum memory location value for returndata 346 | ptr_bounds := add(_returndata_ptr, returndatasize) 347 | // Set new free-memory pointer to point after the returndata in memory 348 | // Returndata is automatically 32-bytes padded 349 | mstore(0x40, add(0x20, ptr_bounds)) 350 | } 351 | } 352 | 353 | /* 354 | Returns the value stored in memory at the pointer. Used to determine the size of fields in returned data 355 | 356 | @param _ptr: A pointer to some location in memory containing returndata 357 | @return length: The value stored at that pointer 358 | */ 359 | function getLength(uint _ptr) internal pure returns (uint length) { 360 | assembly { length := mload(_ptr) } 361 | } 362 | 363 | // Executes the THROWS action, reverting any returned data back to the caller 364 | function doThrow(uint _ptr) internal pure { 365 | assert(getAction(_ptr) == THROWS); 366 | assembly { revert(_ptr, returndatasize) } 367 | } 368 | 369 | /* 370 | Parses and executes a PAYS action copied from returndata and located at the pointer 371 | A PAYS action provides a set of addresses and corresponding amounts of ETH to send to those 372 | addresses. The sender must ensure the call has sufficient funds, or the call will fail 373 | PAYS actions follow a format of: [amt_0][address_0]...[amt_n][address_n] 374 | 375 | @param _ptr: A pointer in memory to an application's returned payment request 376 | @param _ptr_bound: The upper bound on the value for _ptr before it is reading invalid data 377 | @return ptr: An updated pointer, pointing to the end of the PAYS action request in memory 378 | @return n_paid: The number of destinations paid out to from the returned PAYS request 379 | */ 380 | function doPay(bytes32 _exec_id, uint _ptr, uint _ptr_bound) internal returns (uint ptr, uint n_paid) { 381 | // Ensure ETH was sent with the call 382 | require(msg.value > 0); 383 | assert(getAction(_ptr) == PAYS); 384 | _ptr += 4; 385 | // Get number of destinations 386 | uint num_destinations = getLength(_ptr); 387 | _ptr += 32; 388 | address pay_to; 389 | uint amt; 390 | // Loop over PAYS actions and process each one 391 | while (_ptr <= _ptr_bound && n_paid < num_destinations) { 392 | // Get the payment destination and amount from the pointer 393 | assembly { 394 | amt := mload(_ptr) 395 | pay_to := mload(add(0x20, _ptr)) 396 | } 397 | // Invalid address was passed as a payment destination - throw 398 | if (pay_to == address(0) || pay_to == address(this)) 399 | revert('PAYS: invalid destination'); 400 | 401 | // Forward ETH and increment n_paid 402 | address(pay_to).transfer(amt); 403 | n_paid++; 404 | // Increment pointer 405 | _ptr += 64; 406 | // Emit event 407 | emit DeliveredPayment(_exec_id, pay_to, amt); 408 | } 409 | ptr = _ptr; 410 | assert(n_paid == num_destinations); 411 | } 412 | 413 | /* 414 | Parses and executes a STORES action copied from returndata and located at the pointer 415 | A STORES action provides a set of storage locations and corresponding values to store at those locations 416 | true storage locations within this contract are first hashed with the application's execution id to prevent 417 | storage overlaps between applications sharing the contract 418 | STORES actions follow a format of: [location_0][val_0]...[location_n][val_n] 419 | 420 | @param _ptr: A pointer in memory to an application's returned storage request 421 | @param _ptr_bound: The upper bound on the value for _ptr before it is reading invalid data 422 | @param _exec_id: The execution id under which storage is located 423 | @return ptr: An updated pointer, pointing to the end of the STORES action request in memory 424 | @return n_stored: The number of storage locations written to from the returned STORES request 425 | */ 426 | function doStore(uint _ptr, uint _ptr_bound, bytes32 _exec_id) internal returns (uint ptr, uint n_stored) { 427 | assert(getAction(_ptr) == STORES && _exec_id != bytes32(0)); 428 | _ptr += 4; 429 | // Get number of locations to which data will be stored 430 | uint num_locations = getLength(_ptr); 431 | _ptr += 32; 432 | bytes32 location; 433 | bytes32 value; 434 | // Loop over STORES actions and process each one 435 | while (_ptr <= _ptr_bound && n_stored < num_locations) { 436 | // Get storage location and value to store from the pointer 437 | assembly { 438 | location := mload(_ptr) 439 | value := mload(add(0x20, _ptr)) 440 | } 441 | // Store the data to the location hashed with the exec id 442 | store(_exec_id, location, value); 443 | // Increment n_stored and pointer 444 | n_stored++; 445 | _ptr += 64; 446 | } 447 | ptr = _ptr; 448 | require(n_stored == num_locations); 449 | } 450 | 451 | /* 452 | Parses and executes an EMITS action copied from returndata and located at the pointer 453 | An EMITS action is a list of bytes that are able to be processed and passed into logging functions (log0, log1, etc) 454 | EMITS actions follow a format of: [Event_0][Event_1]...[Event_n] 455 | where each Event_i follows the format: [topic_0]...[topic_4][data.length] 456 | -The topics array is a bytes32 array of maximum length 4 and minimum 0 457 | -The final data parameter is a simple bytes array, and is emitted as a non-indexed parameter 458 | 459 | @param _ptr: A pointer in memory to an application's returned emit request 460 | @param _ptr_bound: The upper bound on the value for _ptr before it is reading invalid data 461 | @return ptr: An updated pointer, pointing to the end of the EMITS action request in memory 462 | @return n_emitted: The number of events logged from the returned EMITS request 463 | */ 464 | function doEmit(uint _ptr, uint _ptr_bound) internal returns (uint ptr, uint n_emitted) { 465 | assert(getAction(_ptr) == EMITS); 466 | _ptr += 4; 467 | // Converts number of events that will be emitted 468 | uint num_events = getLength(_ptr); 469 | _ptr += 32; 470 | bytes32[] memory topics; 471 | bytes memory data; 472 | // Loop over EMITS actions and process each one 473 | while (_ptr <= _ptr_bound && n_emitted < num_events) { 474 | // Get array of topics and additional data from the pointer 475 | assembly { 476 | topics := _ptr 477 | data := add(add(_ptr, 0x20), mul(0x20, mload(topics))) 478 | } 479 | // Get size of the Event's data in memory 480 | uint log_size = 32 + (32 * (1 + topics.length)) + data.length; 481 | assembly { 482 | switch mload(topics) // topics.length 483 | case 0 { 484 | // Log Event.data array with no topics 485 | log0( 486 | add(0x20, data), // data(ptr) 487 | mload(data) // data.length 488 | ) 489 | } 490 | case 1 { 491 | // Log Event.data array with 1 topic 492 | log1( 493 | add(0x20, data), // data(ptr) 494 | mload(data), // data.length 495 | mload(add(0x20, topics)) // topics[0] 496 | ) 497 | } 498 | case 2 { 499 | // Log Event.data array with 2 topics 500 | log2( 501 | add(0x20, data), // data(ptr) 502 | mload(data), // data.length 503 | mload(add(0x20, topics)), // topics[0] 504 | mload(add(0x40, topics)) // topics[1] 505 | ) 506 | } 507 | case 3 { 508 | // Log Event.data array with 3 topics 509 | log3( 510 | add(0x20, data), // data(ptr) 511 | mload(data), // data.length 512 | mload(add(0x20, topics)), // topics[0] 513 | mload(add(0x40, topics)), // topics[1] 514 | mload(add(0x60, topics)) // topics[2] 515 | ) 516 | } 517 | case 4 { 518 | // Log Event.data array with 4 topics 519 | log4( 520 | add(0x20, data), // data(ptr) 521 | mload(data), // data.length 522 | mload(add(0x20, topics)), // topics[0] 523 | mload(add(0x40, topics)), // topics[1] 524 | mload(add(0x60, topics)), // topics[2] 525 | mload(add(0x80, topics)) // topics[3] 526 | ) 527 | } 528 | default { 529 | // Events must have 4 or fewer topics 530 | mstore(0, 'EMITS: invalid topic count') 531 | revert(0, 0x20) 532 | } 533 | } 534 | // Event emitted - increment n_emitted and pointer 535 | n_emitted++; 536 | _ptr += log_size; 537 | } 538 | ptr = _ptr; 539 | require(n_emitted == num_events); 540 | } 541 | 542 | // Return the bytes4 action requestor stored at the pointer, and cleans the remaining bytes 543 | function getAction(uint _ptr) internal pure returns (bytes4 action) { 544 | assembly { 545 | // Get the first 4 bytes stored at the pointer, and clean the rest of the bytes remaining 546 | action := and(mload(_ptr), 0xffffffff00000000000000000000000000000000000000000000000000000000) 547 | } 548 | } 549 | 550 | // Sets the execution id and sender address in special storage locations, so that 551 | // they are able to be read by the target application 552 | function setContext(bytes32 _exec_id, address _sender) internal { 553 | // Ensure the exec id and sender are nonzero 554 | assert(_exec_id != bytes32(0) && _sender != address(0)); 555 | exec_id = _exec_id; 556 | sender = _sender; 557 | } 558 | 559 | // Stores data to a given location, with a key (exec id) 560 | function store(bytes32 _exec_id, bytes32 _location, bytes32 _data) internal { 561 | // Get true location to store data to - hash of location hashed with exec id 562 | _location = keccak256(_location, _exec_id); 563 | // Store data at location 564 | assembly { sstore(_location, _data) } 565 | } 566 | 567 | // STORAGE READS // 568 | 569 | /* 570 | Returns data stored at a given location 571 | @param _location: The address to get data from 572 | @return data: The data stored at the location after hashing 573 | */ 574 | function read(bytes32 _exec_id, bytes32 _location) public view returns (bytes32 data_read) { 575 | _location = keccak256(_location, _exec_id); 576 | assembly { data_read := sload(_location) } 577 | } 578 | 579 | /* 580 | Returns data stored in several nonconsecutive locations 581 | @param _locations: A dynamic array of storage locations to read from 582 | @return data_read: The corresponding data stored in the requested locations 583 | */ 584 | function readMulti(bytes32 _exec_id, bytes32[] _locations) public view returns (bytes32[] data_read) { 585 | data_read = new bytes32[](_locations.length); 586 | for (uint i = 0; i < _locations.length; i++) { 587 | data_read[i] = read(_exec_id, _locations[i]); 588 | } 589 | } 590 | } 591 | -------------------------------------------------------------------------------- /contracts/core/Contract.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | import '../lib/SafeMath.sol'; 4 | 5 | library Contract { 6 | 7 | using SafeMath for uint; 8 | 9 | // Modifiers: // 10 | 11 | // Runs two functions before and after a function - 12 | modifier conditions(function () pure first, function () pure last) { 13 | first(); 14 | _; 15 | last(); 16 | } 17 | 18 | bytes32 internal constant EXEC_PERMISSIONS = keccak256('script_exec_permissions'); 19 | 20 | // Sets up contract execution - reads execution id and sender from storage and 21 | // places in memory, creating getters. Calling this function should be the first 22 | // action an application does as part of execution, as it sets up memory for 23 | // execution. Additionally, application functions in the main file should be 24 | // external, so that memory is not touched prior to calling this function. 25 | // The 3rd slot allocated will hold a pointer to a storage buffer, which will 26 | // be reverted to abstract storage to store data, emit events, and forward 27 | // wei on behalf of the application. 28 | function authorize(address _script_exec) internal view { 29 | // Initialize memory 30 | initialize(); 31 | 32 | // Check that the sender is authorized as a script exec contract for this exec id 33 | bytes32 perms = EXEC_PERMISSIONS; 34 | bool authorized; 35 | assembly { 36 | // Place the script exec address at 0, and the exec permissions seed after it 37 | mstore(0, _script_exec) 38 | mstore(0x20, perms) 39 | // Hash the resulting 0x34 bytes, and place back into memory at 0 40 | mstore(0, keccak256(0x0c, 0x34)) 41 | // Place the exec id after the hash - 42 | mstore(0x20, mload(0x80)) 43 | // Hash the previous hash with the execution id, and check the result 44 | authorized := sload(keccak256(0, 0x40)) 45 | } 46 | if (!authorized) 47 | revert("Sender is not authorized as a script exec address"); 48 | } 49 | 50 | // Sets up contract execution when initializing an instance of the application 51 | // First, reads execution id and sender from storage (execution id should be 0xDEAD), 52 | // then places them in memory, creating getters. Calling this function should be the first 53 | // action an application does as part of execution, as it sets up memory for 54 | // execution. Additionally, application functions in the main file should be 55 | // external, so that memory is not touched prior to calling this function. 56 | // The 3rd slot allocated will hold a pointer to a storage buffer, which will 57 | // be reverted to abstract storage to store data, emit events, and forward 58 | // wei on behalf of the application. 59 | function initialize() internal view { 60 | // No memory should have been allocated yet - expect the free memory pointer 61 | // to point to 0x80 - and throw if it does not 62 | require(freeMem() == 0x80, "Memory allocated prior to execution"); 63 | // Next, set up memory for execution 64 | assembly { 65 | mstore(0x80, sload(0)) // Execution id, read from storage 66 | mstore(0xa0, sload(1)) // Original sender address, read from storage 67 | mstore(0xc0, 0) // Pointer to storage buffer 68 | mstore(0xe0, 0) // Bytes4 value of the current action requestor being used 69 | mstore(0x100, 0) // Enum representing the next type of function to be called (when pushing to buffer) 70 | mstore(0x120, 0) // Number of storage slots written to in buffer 71 | mstore(0x140, 0) // Number of events pushed to buffer 72 | mstore(0x160, 0) // Number of payment destinations pushed to buffer 73 | 74 | // Update free memory pointer - 75 | mstore(0x40, 0x180) 76 | } 77 | // Ensure that the sender and execution id returned from storage are expected values - 78 | assert(execID() != bytes32(0) && sender() != address(0)); 79 | } 80 | 81 | // Calls the passed-in function, performing a memory state check before and after the check 82 | // is executed. 83 | function checks(function () view _check) conditions(validState, validState) internal view { 84 | _check(); 85 | } 86 | 87 | // Calls the passed-in function, performing a memory state check before and after the check 88 | // is executed. 89 | function checks(function () pure _check) conditions(validState, validState) internal pure { 90 | _check(); 91 | } 92 | 93 | // Ensures execution completed successfully, and reverts the created storage buffer 94 | // back to the sender. 95 | function commit() conditions(validState, none) internal pure { 96 | // Check value of storage buffer pointer - should be at least 0x180 97 | bytes32 ptr = buffPtr(); 98 | require(ptr >= 0x180, "Invalid buffer pointer"); 99 | 100 | assembly { 101 | // Get the size of the buffer 102 | let size := mload(add(0x20, ptr)) 103 | mstore(ptr, 0x20) // Place dynamic data offset before buffer 104 | // Revert to storage 105 | revert(ptr, add(0x40, size)) 106 | } 107 | } 108 | 109 | // Helpers: // 110 | 111 | // Checks to ensure the application was correctly executed - 112 | function validState() private pure { 113 | if (freeMem() < 0x180) 114 | revert('Expected Contract.execute()'); 115 | 116 | if (buffPtr() != 0 && buffPtr() < 0x180) 117 | revert('Invalid buffer pointer'); 118 | 119 | assert(execID() != bytes32(0) && sender() != address(0)); 120 | } 121 | 122 | // Returns a pointer to the execution storage buffer - 123 | function buffPtr() private pure returns (bytes32 ptr) { 124 | assembly { ptr := mload(0xc0) } 125 | } 126 | 127 | // Returns the location pointed to by the free memory pointer - 128 | function freeMem() private pure returns (bytes32 ptr) { 129 | assembly { ptr := mload(0x40) } 130 | } 131 | 132 | // Returns the current storage action 133 | function currentAction() private pure returns (bytes4 action) { 134 | if (buffPtr() == bytes32(0)) 135 | return bytes4(0); 136 | 137 | assembly { action := mload(0xe0) } 138 | } 139 | 140 | // If the current action is not storing, reverts 141 | function isStoring() private pure { 142 | if (currentAction() != STORES) 143 | revert('Invalid current action - expected STORES'); 144 | } 145 | 146 | // If the current action is not emitting, reverts 147 | function isEmitting() private pure { 148 | if (currentAction() != EMITS) 149 | revert('Invalid current action - expected EMITS'); 150 | } 151 | 152 | // If the current action is not paying, reverts 153 | function isPaying() private pure { 154 | if (currentAction() != PAYS) 155 | revert('Invalid current action - expected PAYS'); 156 | } 157 | 158 | // Initializes a storage buffer in memory - 159 | function startBuffer() private pure { 160 | assembly { 161 | // Get a pointer to free memory, and place at 0xc0 (storage buffer pointer) 162 | let ptr := msize() 163 | mstore(0xc0, ptr) 164 | // Clear bytes at pointer - 165 | mstore(ptr, 0) // temp ptr 166 | mstore(add(0x20, ptr), 0) // buffer length 167 | // Update free memory pointer - 168 | mstore(0x40, add(0x40, ptr)) 169 | // Set expected next function to 'NONE' - 170 | mstore(0x100, 1) 171 | } 172 | } 173 | 174 | // Checks whether or not it is valid to create a STORES action request - 175 | function validStoreBuff() private pure { 176 | // Get pointer to current buffer - if zero, create a new buffer - 177 | if (buffPtr() == bytes32(0)) 178 | startBuffer(); 179 | 180 | // Ensure that the current action is not 'storing', and that the buffer has not already 181 | // completed a STORES action - 182 | if (stored() != 0 || currentAction() == STORES) 183 | revert('Duplicate request - stores'); 184 | } 185 | 186 | // Checks whether or not it is valid to create an EMITS action request - 187 | function validEmitBuff() private pure { 188 | // Get pointer to current buffer - if zero, create a new buffer - 189 | if (buffPtr() == bytes32(0)) 190 | startBuffer(); 191 | 192 | // Ensure that the current action is not 'emitting', and that the buffer has not already 193 | // completed an EMITS action - 194 | if (emitted() != 0 || currentAction() == EMITS) 195 | revert('Duplicate request - emits'); 196 | } 197 | 198 | // Checks whether or not it is valid to create a PAYS action request - 199 | function validPayBuff() private pure { 200 | // Get pointer to current buffer - if zero, create a new buffer - 201 | if (buffPtr() == bytes32(0)) 202 | startBuffer(); 203 | 204 | // Ensure that the current action is not 'paying', and that the buffer has not already 205 | // completed an PAYS action - 206 | if (paid() != 0 || currentAction() == PAYS) 207 | revert('Duplicate request - pays'); 208 | } 209 | 210 | // Placeholder function when no pre or post condition for a function is needed 211 | function none() private pure { } 212 | 213 | // Runtime getters: // 214 | 215 | // Returns the execution id from memory - 216 | function execID() internal pure returns (bytes32 exec_id) { 217 | assembly { exec_id := mload(0x80) } 218 | require(exec_id != bytes32(0), "Execution id overwritten, or not read"); 219 | } 220 | 221 | // Returns the original sender from memory - 222 | function sender() internal pure returns (address addr) { 223 | assembly { addr := mload(0xa0) } 224 | require(addr != address(0), "Sender address overwritten, or not read"); 225 | } 226 | 227 | // Reading from storage: // 228 | 229 | // Reads from storage, resolving the passed-in location to its true location in storage 230 | // by hashing with the exec id. Returns the data read from that location 231 | function read(bytes32 _location) internal view returns (bytes32 data) { 232 | data = keccak256(_location, execID()); 233 | assembly { data := sload(data) } 234 | } 235 | 236 | // Storing data, emitting events, and forwarding payments: // 237 | 238 | bytes4 internal constant EMITS = bytes4(keccak256('Emit((bytes32[],bytes)[])')); 239 | bytes4 internal constant STORES = bytes4(keccak256('Store(bytes32[])')); 240 | bytes4 internal constant PAYS = bytes4(keccak256('Pay(bytes32[])')); 241 | bytes4 internal constant THROWS = bytes4(keccak256('Error(string)')); 242 | 243 | // Function enums - 244 | enum NextFunction { 245 | INVALID, NONE, STORE_DEST, VAL_SET, VAL_INC, VAL_DEC, EMIT_LOG, PAY_DEST, PAY_AMT 246 | } 247 | 248 | // Checks that a call pushing a storage destination to the buffer is expected and valid 249 | function validStoreDest() private pure { 250 | // Ensure that the next function expected pushes a storage destination - 251 | if (expected() != NextFunction.STORE_DEST) 252 | revert('Unexpected function order - expected storage destination to be pushed'); 253 | 254 | // Ensure that the current buffer is pushing STORES actions - 255 | isStoring(); 256 | } 257 | 258 | // Checks that a call pushing a storage value to the buffer is expected and valid 259 | function validStoreVal() private pure { 260 | // Ensure that the next function expected pushes a storage value - 261 | if ( 262 | expected() != NextFunction.VAL_SET && 263 | expected() != NextFunction.VAL_INC && 264 | expected() != NextFunction.VAL_DEC 265 | ) revert('Unexpected function order - expected storage value to be pushed'); 266 | 267 | // Ensure that the current buffer is pushing STORES actions - 268 | isStoring(); 269 | } 270 | 271 | // Checks that a call pushing a payment destination to the buffer is expected and valid 272 | function validPayDest() private pure { 273 | // Ensure that the next function expected pushes a payment destination - 274 | if (expected() != NextFunction.PAY_DEST) 275 | revert('Unexpected function order - expected payment destination to be pushed'); 276 | 277 | // Ensure that the current buffer is pushing PAYS actions - 278 | isPaying(); 279 | } 280 | 281 | // Checks that a call pushing a payment amount to the buffer is expected and valid 282 | function validPayAmt() private pure { 283 | // Ensure that the next function expected pushes a payment amount - 284 | if (expected() != NextFunction.PAY_AMT) 285 | revert('Unexpected function order - expected payment amount to be pushed'); 286 | 287 | // Ensure that the current buffer is pushing PAYS actions - 288 | isPaying(); 289 | } 290 | 291 | // Checks that a call pushing an event to the buffer is expected and valid 292 | function validEvent() private pure { 293 | // Ensure that the next function expected pushes an event - 294 | if (expected() != NextFunction.EMIT_LOG) 295 | revert('Unexpected function order - expected event to be pushed'); 296 | 297 | // Ensure that the current buffer is pushing EMITS actions - 298 | isEmitting(); 299 | } 300 | 301 | // Begins creating a storage buffer - values and locations pushed will be committed 302 | // to storage at the end of execution 303 | function storing() conditions(validStoreBuff, isStoring) internal pure { 304 | bytes4 action_req = STORES; 305 | assembly { 306 | // Get pointer to buffer length - 307 | let ptr := add(0x20, mload(0xc0)) 308 | // Push requestor to the end of buffer, as well as to the 'current action' slot - 309 | mstore(add(0x20, add(ptr, mload(ptr))), action_req) 310 | // Push '0' to the end of the 4 bytes just pushed - this will be the length of the STORES action 311 | mstore(add(0x24, add(ptr, mload(ptr))), 0) 312 | // Increment buffer length - 0x24 plus the previous length 313 | mstore(ptr, add(0x24, mload(ptr))) 314 | // Set the current action being executed (STORES) - 315 | mstore(0xe0, action_req) 316 | // Set the expected next function - STORE_DEST 317 | mstore(0x100, 2) 318 | // Set a pointer to the length of the current request within the buffer 319 | mstore(sub(ptr, 0x20), add(ptr, mload(ptr))) 320 | } 321 | // Update free memory pointer 322 | setFreeMem(); 323 | } 324 | 325 | // Sets a passed in location to a value passed in via 'to' 326 | function set(bytes32 _field) conditions(validStoreDest, validStoreVal) internal pure returns (bytes32) { 327 | assembly { 328 | // Get pointer to buffer length - 329 | let ptr := add(0x20, mload(0xc0)) 330 | // Push storage destination to the end of the buffer - 331 | mstore(add(0x20, add(ptr, mload(ptr))), _field) 332 | // Increment buffer length - 0x20 plus the previous length 333 | mstore(ptr, add(0x20, mload(ptr))) 334 | // Set the expected next function - VAL_SET 335 | mstore(0x100, 3) 336 | // Increment STORES action length - 337 | mstore( 338 | mload(sub(ptr, 0x20)), 339 | add(1, mload(mload(sub(ptr, 0x20)))) 340 | ) 341 | // Update number of storage slots pushed to - 342 | mstore(0x120, add(1, mload(0x120))) 343 | } 344 | // Update free memory pointer 345 | setFreeMem(); 346 | return _field; 347 | } 348 | 349 | // Sets a previously-passed-in destination in storage to the value 350 | function to(bytes32, bytes32 _val) conditions(validStoreVal, validStoreDest) internal pure { 351 | assembly { 352 | // Get pointer to buffer length - 353 | let ptr := add(0x20, mload(0xc0)) 354 | // Push storage value to the end of the buffer - 355 | mstore(add(0x20, add(ptr, mload(ptr))), _val) 356 | // Increment buffer length - 0x20 plus the previous length 357 | mstore(ptr, add(0x20, mload(ptr))) 358 | // Set the expected next function - STORE_DEST 359 | mstore(0x100, 2) 360 | } 361 | // Update free memory pointer 362 | setFreeMem(); 363 | } 364 | 365 | // Sets a previously-passed-in destination in storage to the value 366 | function to(bytes32 _field, uint _val) internal pure { 367 | to(_field, bytes32(_val)); 368 | } 369 | 370 | // Sets a previously-passed-in destination in storage to the value 371 | function to(bytes32 _field, address _val) internal pure { 372 | to(_field, bytes32(_val)); 373 | } 374 | 375 | // Sets a previously-passed-in destination in storage to the value 376 | function to(bytes32 _field, bool _val) internal pure { 377 | to( 378 | _field, 379 | _val ? bytes32(1) : bytes32(0) 380 | ); 381 | } 382 | 383 | function increase(bytes32 _field) conditions(validStoreDest, validStoreVal) internal view returns (bytes32 val) { 384 | // Read value stored at the location in storage - 385 | val = keccak256(_field, execID()); 386 | assembly { 387 | val := sload(val) 388 | // Get pointer to buffer length - 389 | let ptr := add(0x20, mload(0xc0)) 390 | // Push storage destination to the end of the buffer - 391 | mstore(add(0x20, add(ptr, mload(ptr))), _field) 392 | // Increment buffer length - 0x20 plus the previous length 393 | mstore(ptr, add(0x20, mload(ptr))) 394 | // Set the expected next function - VAL_INC 395 | mstore(0x100, 4) 396 | // Increment STORES action length - 397 | mstore( 398 | mload(sub(ptr, 0x20)), 399 | add(1, mload(mload(sub(ptr, 0x20)))) 400 | ) 401 | // Update number of storage slots pushed to - 402 | mstore(0x120, add(1, mload(0x120))) 403 | } 404 | // Update free memory pointer 405 | setFreeMem(); 406 | return val; 407 | } 408 | 409 | function decrease(bytes32 _field) conditions(validStoreDest, validStoreVal) internal view returns (bytes32 val) { 410 | // Read value stored at the location in storage - 411 | val = keccak256(_field, execID()); 412 | assembly { 413 | val := sload(val) 414 | // Get pointer to buffer length - 415 | let ptr := add(0x20, mload(0xc0)) 416 | // Push storage destination to the end of the buffer - 417 | mstore(add(0x20, add(ptr, mload(ptr))), _field) 418 | // Increment buffer length - 0x20 plus the previous length 419 | mstore(ptr, add(0x20, mload(ptr))) 420 | // Set the expected next function - VAL_DEC 421 | mstore(0x100, 5) 422 | // Increment STORES action length - 423 | mstore( 424 | mload(sub(ptr, 0x20)), 425 | add(1, mload(mload(sub(ptr, 0x20)))) 426 | ) 427 | // Update number of storage slots pushed to - 428 | mstore(0x120, add(1, mload(0x120))) 429 | } 430 | // Update free memory pointer 431 | setFreeMem(); 432 | return val; 433 | } 434 | 435 | function by(bytes32 _val, uint _amt) conditions(validStoreVal, validStoreDest) internal pure { 436 | // Check the expected function type - if it is VAL_INC, perform safe-add on the value 437 | // If it is VAL_DEC, perform safe-sub on the value 438 | if (expected() == NextFunction.VAL_INC) 439 | _amt = _amt.add(uint(_val)); 440 | else if (expected() == NextFunction.VAL_DEC) 441 | _amt = uint(_val).sub(_amt); 442 | else 443 | revert('Expected VAL_INC or VAL_DEC'); 444 | 445 | assembly { 446 | // Get pointer to buffer length - 447 | let ptr := add(0x20, mload(0xc0)) 448 | // Push storage value to the end of the buffer - 449 | mstore(add(0x20, add(ptr, mload(ptr))), _amt) 450 | // Increment buffer length - 0x20 plus the previous length 451 | mstore(ptr, add(0x20, mload(ptr))) 452 | // Set the expected next function - STORE_DEST 453 | mstore(0x100, 2) 454 | } 455 | // Update free memory pointer 456 | setFreeMem(); 457 | } 458 | 459 | // Decreases the value at some field by a maximum amount, and sets it to 0 if there will be underflow 460 | function byMaximum(bytes32 _val, uint _amt) conditions(validStoreVal, validStoreDest) internal pure { 461 | // Check the expected function type - if it is VAL_DEC, set the new amount to the difference of 462 | // _val and _amt, to a minimum of 0 463 | if (expected() == NextFunction.VAL_DEC) { 464 | if (_amt >= uint(_val)) 465 | _amt = 0; 466 | else 467 | _amt = uint(_val).sub(_amt); 468 | } else { 469 | revert('Expected VAL_DEC'); 470 | } 471 | 472 | assembly { 473 | // Get pointer to buffer length - 474 | let ptr := add(0x20, mload(0xc0)) 475 | // Push storage value to the end of the buffer - 476 | mstore(add(0x20, add(ptr, mload(ptr))), _amt) 477 | // Increment buffer length - 0x20 plus the previous length 478 | mstore(ptr, add(0x20, mload(ptr))) 479 | // Set the expected next function - STORE_DEST 480 | mstore(0x100, 2) 481 | } 482 | // Update free memory pointer 483 | setFreeMem(); 484 | } 485 | 486 | // Begins creating an event log buffer - topics and data pushed will be emitted by 487 | // storage at the end of execution 488 | function emitting() conditions(validEmitBuff, isEmitting) internal pure { 489 | bytes4 action_req = EMITS; 490 | assembly { 491 | // Get pointer to buffer length - 492 | let ptr := add(0x20, mload(0xc0)) 493 | // Push requestor to the end of buffer, as well as to the 'current action' slot - 494 | mstore(add(0x20, add(ptr, mload(ptr))), action_req) 495 | // Push '0' to the end of the 4 bytes just pushed - this will be the length of the EMITS action 496 | mstore(add(0x24, add(ptr, mload(ptr))), 0) 497 | // Increment buffer length - 0x24 plus the previous length 498 | mstore(ptr, add(0x24, mload(ptr))) 499 | // Set the current action being executed (EMITS) - 500 | mstore(0xe0, action_req) 501 | // Set the expected next function - EMIT_LOG 502 | mstore(0x100, 6) 503 | // Set a pointer to the length of the current request within the buffer 504 | mstore(sub(ptr, 0x20), add(ptr, mload(ptr))) 505 | } 506 | // Update free memory pointer 507 | setFreeMem(); 508 | } 509 | 510 | function log(bytes32 _data) conditions(validEvent, validEvent) internal pure { 511 | assembly { 512 | // Get pointer to buffer length - 513 | let ptr := add(0x20, mload(0xc0)) 514 | // Push 0 to the end of the buffer - event will have 0 topics 515 | mstore(add(0x20, add(ptr, mload(ptr))), 0) 516 | // If _data is zero, set data size to 0 in buffer and push - 517 | if eq(_data, 0) { 518 | mstore(add(0x40, add(ptr, mload(ptr))), 0) 519 | // Increment buffer length - 0x40 plus the original length 520 | mstore(ptr, add(0x40, mload(ptr))) 521 | } 522 | // If _data is not zero, set size to 0x20 and push to buffer - 523 | if iszero(eq(_data, 0)) { 524 | // Push data size (0x20) to the end of the buffer 525 | mstore(add(0x40, add(ptr, mload(ptr))), 0x20) 526 | // Push data to the end of the buffer 527 | mstore(add(0x60, add(ptr, mload(ptr))), _data) 528 | // Increment buffer length - 0x60 plus the original length 529 | mstore(ptr, add(0x60, mload(ptr))) 530 | } 531 | // Increment EMITS action length - 532 | mstore( 533 | mload(sub(ptr, 0x20)), 534 | add(1, mload(mload(sub(ptr, 0x20)))) 535 | ) 536 | // Update number of events pushed to buffer - 537 | mstore(0x140, add(1, mload(0x140))) 538 | } 539 | // Update free memory pointer 540 | setFreeMem(); 541 | } 542 | 543 | function log(bytes32[1] memory _topics, bytes32 _data) conditions(validEvent, validEvent) internal pure { 544 | assembly { 545 | // Get pointer to buffer length - 546 | let ptr := add(0x20, mload(0xc0)) 547 | // Push 1 to the end of the buffer - event will have 1 topic 548 | mstore(add(0x20, add(ptr, mload(ptr))), 1) 549 | // Push topic to end of buffer 550 | mstore(add(0x40, add(ptr, mload(ptr))), mload(_topics)) 551 | // If _data is zero, set data size to 0 in buffer and push - 552 | if eq(_data, 0) { 553 | mstore(add(0x60, add(ptr, mload(ptr))), 0) 554 | // Increment buffer length - 0x60 plus the original length 555 | mstore(ptr, add(0x60, mload(ptr))) 556 | } 557 | // If _data is not zero, set size to 0x20 and push to buffer - 558 | if iszero(eq(_data, 0)) { 559 | // Push data size (0x20) to the end of the buffer 560 | mstore(add(0x60, add(ptr, mload(ptr))), 0x20) 561 | // Push data to the end of the buffer 562 | mstore(add(0x80, add(ptr, mload(ptr))), _data) 563 | // Increment buffer length - 0x80 plus the original length 564 | mstore(ptr, add(0x80, mload(ptr))) 565 | } 566 | // Increment EMITS action length - 567 | mstore( 568 | mload(sub(ptr, 0x20)), 569 | add(1, mload(mload(sub(ptr, 0x20)))) 570 | ) 571 | // Update number of events pushed to buffer - 572 | mstore(0x140, add(1, mload(0x140))) 573 | } 574 | // Update free memory pointer 575 | setFreeMem(); 576 | } 577 | 578 | function log(bytes32[2] memory _topics, bytes32 _data) conditions(validEvent, validEvent) internal pure { 579 | assembly { 580 | // Get pointer to buffer length - 581 | let ptr := add(0x20, mload(0xc0)) 582 | // Push 2 to the end of the buffer - event will have 2 topics 583 | mstore(add(0x20, add(ptr, mload(ptr))), 2) 584 | // Push topics to end of buffer 585 | mstore(add(0x40, add(ptr, mload(ptr))), mload(_topics)) 586 | mstore(add(0x60, add(ptr, mload(ptr))), mload(add(0x20, _topics))) 587 | // If _data is zero, set data size to 0 in buffer and push - 588 | if eq(_data, 0) { 589 | mstore(add(0x80, add(ptr, mload(ptr))), 0) 590 | // Increment buffer length - 0x80 plus the original length 591 | mstore(ptr, add(0x80, mload(ptr))) 592 | } 593 | // If _data is not zero, set size to 0x20 and push to buffer - 594 | if iszero(eq(_data, 0)) { 595 | // Push data size (0x20) to the end of the buffer 596 | mstore(add(0x80, add(ptr, mload(ptr))), 0x20) 597 | // Push data to the end of the buffer 598 | mstore(add(0xa0, add(ptr, mload(ptr))), _data) 599 | // Increment buffer length - 0xa0 plus the original length 600 | mstore(ptr, add(0xa0, mload(ptr))) 601 | } 602 | // Increment EMITS action length - 603 | mstore( 604 | mload(sub(ptr, 0x20)), 605 | add(1, mload(mload(sub(ptr, 0x20)))) 606 | ) 607 | // Update number of events pushed to buffer - 608 | mstore(0x140, add(1, mload(0x140))) 609 | } 610 | // Update free memory pointer 611 | setFreeMem(); 612 | } 613 | 614 | function log(bytes32[3] memory _topics, bytes32 _data) conditions(validEvent, validEvent) internal pure { 615 | assembly { 616 | // Get pointer to buffer length - 617 | let ptr := add(0x20, mload(0xc0)) 618 | // Push 3 to the end of the buffer - event will have 3 topics 619 | mstore(add(0x20, add(ptr, mload(ptr))), 3) 620 | // Push topics to end of buffer 621 | mstore(add(0x40, add(ptr, mload(ptr))), mload(_topics)) 622 | mstore(add(0x60, add(ptr, mload(ptr))), mload(add(0x20, _topics))) 623 | mstore(add(0x80, add(ptr, mload(ptr))), mload(add(0x40, _topics))) 624 | // If _data is zero, set data size to 0 in buffer and push - 625 | if eq(_data, 0) { 626 | mstore(add(0xa0, add(ptr, mload(ptr))), 0) 627 | // Increment buffer length - 0xa0 plus the original length 628 | mstore(ptr, add(0xa0, mload(ptr))) 629 | } 630 | // If _data is not zero, set size to 0x20 and push to buffer - 631 | if iszero(eq(_data, 0)) { 632 | // Push data size (0x20) to the end of the buffer 633 | mstore(add(0xa0, add(ptr, mload(ptr))), 0x20) 634 | // Push data to the end of the buffer 635 | mstore(add(0xc0, add(ptr, mload(ptr))), _data) 636 | // Increment buffer length - 0xc0 plus the original length 637 | mstore(ptr, add(0xc0, mload(ptr))) 638 | } 639 | // Increment EMITS action length - 640 | mstore( 641 | mload(sub(ptr, 0x20)), 642 | add(1, mload(mload(sub(ptr, 0x20)))) 643 | ) 644 | // Update number of events pushed to buffer - 645 | mstore(0x140, add(1, mload(0x140))) 646 | } 647 | // Update free memory pointer 648 | setFreeMem(); 649 | } 650 | 651 | function log(bytes32[4] memory _topics, bytes32 _data) conditions(validEvent, validEvent) internal pure { 652 | assembly { 653 | // Get pointer to buffer length - 654 | let ptr := add(0x20, mload(0xc0)) 655 | // Push 4 to the end of the buffer - event will have 4 topics 656 | mstore(add(0x20, add(ptr, mload(ptr))), 4) 657 | // Push topics to end of buffer 658 | mstore(add(0x40, add(ptr, mload(ptr))), mload(_topics)) 659 | mstore(add(0x60, add(ptr, mload(ptr))), mload(add(0x20, _topics))) 660 | mstore(add(0x80, add(ptr, mload(ptr))), mload(add(0x40, _topics))) 661 | mstore(add(0xa0, add(ptr, mload(ptr))), mload(add(0x60, _topics))) 662 | // If _data is zero, set data size to 0 in buffer and push - 663 | if eq(_data, 0) { 664 | mstore(add(0xc0, add(ptr, mload(ptr))), 0) 665 | // Increment buffer length - 0xc0 plus the original length 666 | mstore(ptr, add(0xc0, mload(ptr))) 667 | } 668 | // If _data is not zero, set size to 0x20 and push to buffer - 669 | if iszero(eq(_data, 0)) { 670 | // Push data size (0x20) to the end of the buffer 671 | mstore(add(0xc0, add(ptr, mload(ptr))), 0x20) 672 | // Push data to the end of the buffer 673 | mstore(add(0xe0, add(ptr, mload(ptr))), _data) 674 | // Increment buffer length - 0xe0 plus the original length 675 | mstore(ptr, add(0xe0, mload(ptr))) 676 | } 677 | // Increment EMITS action length - 678 | mstore( 679 | mload(sub(ptr, 0x20)), 680 | add(1, mload(mload(sub(ptr, 0x20)))) 681 | ) 682 | // Update number of events pushed to buffer - 683 | mstore(0x140, add(1, mload(0x140))) 684 | } 685 | // Update free memory pointer 686 | setFreeMem(); 687 | } 688 | 689 | // Begins creating a storage buffer - destinations entered will be forwarded wei 690 | // before the end of execution 691 | function paying() conditions(validPayBuff, isPaying) internal pure { 692 | bytes4 action_req = PAYS; 693 | assembly { 694 | // Get pointer to buffer length - 695 | let ptr := add(0x20, mload(0xc0)) 696 | // Push requestor to the end of buffer, as well as to the 'current action' slot - 697 | mstore(add(0x20, add(ptr, mload(ptr))), action_req) 698 | // Push '0' to the end of the 4 bytes just pushed - this will be the length of the PAYS action 699 | mstore(add(0x24, add(ptr, mload(ptr))), 0) 700 | // Increment buffer length - 0x24 plus the previous length 701 | mstore(ptr, add(0x24, mload(ptr))) 702 | // Set the current action being executed (PAYS) - 703 | mstore(0xe0, action_req) 704 | // Set the expected next function - PAY_AMT 705 | mstore(0x100, 8) 706 | // Set a pointer to the length of the current request within the buffer 707 | mstore(sub(ptr, 0x20), add(ptr, mload(ptr))) 708 | } 709 | // Update free memory pointer 710 | setFreeMem(); 711 | } 712 | 713 | // Pushes an amount of wei to forward to the buffer 714 | function pay(uint _amount) conditions(validPayAmt, validPayDest) internal pure returns (uint) { 715 | assembly { 716 | // Get pointer to buffer length - 717 | let ptr := add(0x20, mload(0xc0)) 718 | // Push payment amount to the end of the buffer - 719 | mstore(add(0x20, add(ptr, mload(ptr))), _amount) 720 | // Increment buffer length - 0x20 plus the previous length 721 | mstore(ptr, add(0x20, mload(ptr))) 722 | // Set the expected next function - PAY_DEST 723 | mstore(0x100, 7) 724 | // Increment PAYS action length - 725 | mstore( 726 | mload(sub(ptr, 0x20)), 727 | add(1, mload(mload(sub(ptr, 0x20)))) 728 | ) 729 | // Update number of payment destinations to be pushed to - 730 | mstore(0x160, add(1, mload(0x160))) 731 | } 732 | // Update free memory pointer 733 | setFreeMem(); 734 | return _amount; 735 | } 736 | 737 | // Push an address to forward wei to, to the buffer 738 | function toAcc(uint, address _dest) conditions(validPayDest, validPayAmt) internal pure { 739 | assembly { 740 | // Get pointer to buffer length - 741 | let ptr := add(0x20, mload(0xc0)) 742 | // Push payment destination to the end of the buffer - 743 | mstore(add(0x20, add(ptr, mload(ptr))), _dest) 744 | // Increment buffer length - 0x20 plus the previous length 745 | mstore(ptr, add(0x20, mload(ptr))) 746 | // Set the expected next function - PAY_AMT 747 | mstore(0x100, 8) 748 | } 749 | // Update free memory pointer 750 | setFreeMem(); 751 | } 752 | 753 | // Sets the free memory pointer to point beyond all accessed memory 754 | function setFreeMem() private pure { 755 | assembly { mstore(0x40, msize) } 756 | } 757 | 758 | // Returns the enum representing the next expected function to be called - 759 | function expected() private pure returns (NextFunction next) { 760 | assembly { next := mload(0x100) } 761 | } 762 | 763 | // Returns the number of events pushed to the storage buffer - 764 | function emitted() internal pure returns (uint num_emitted) { 765 | if (buffPtr() == bytes32(0)) 766 | return 0; 767 | 768 | // Load number emitted from buffer - 769 | assembly { num_emitted := mload(0x140) } 770 | } 771 | 772 | // Returns the number of storage slots pushed to the storage buffer - 773 | function stored() internal pure returns (uint num_stored) { 774 | if (buffPtr() == bytes32(0)) 775 | return 0; 776 | 777 | // Load number stored from buffer - 778 | assembly { num_stored := mload(0x120) } 779 | } 780 | 781 | // Returns the number of payment destinations and amounts pushed to the storage buffer - 782 | function paid() internal pure returns (uint num_paid) { 783 | if (buffPtr() == bytes32(0)) 784 | return 0; 785 | 786 | // Load number paid from buffer - 787 | assembly { num_paid := mload(0x160) } 788 | } 789 | } 790 | -------------------------------------------------------------------------------- /contracts/core/Proxy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | import '../interfaces/StorageInterface.sol'; 4 | 5 | contract Proxy { 6 | 7 | // Registry storage 8 | address public proxy_admin; 9 | StorageInterface public app_storage; 10 | bytes32 public registry_exec_id; 11 | address public provider; 12 | bytes32 public app_name; 13 | 14 | // App storage 15 | bytes32 public app_version; 16 | bytes32 public app_exec_id; 17 | address public app_index; 18 | 19 | // Function selector for storage 'exec' function 20 | bytes4 internal constant EXEC_SEL = bytes4(keccak256('exec(address,bytes32,bytes)')); 21 | 22 | // Event emitted in case of a revert from storage 23 | event StorageException(bytes32 indexed execution_id, string message); 24 | 25 | // For storage refunds 26 | function () external payable { 27 | require(msg.sender == address(app_storage)); 28 | } 29 | 30 | // Constructor - sets proxy admin, as well as initial variables 31 | constructor (address _storage, bytes32 _registry_exec_id, address _provider, bytes32 _app_name) public { 32 | proxy_admin = msg.sender; 33 | app_storage = StorageInterface(_storage); 34 | registry_exec_id = _registry_exec_id; 35 | provider = _provider; 36 | app_name = _app_name; 37 | } 38 | 39 | // Declare abstract execution function - 40 | function exec(bytes _calldata) external payable returns (bool); 41 | 42 | // Checks to see if an error message was returned with the failed call, and emits it if so - 43 | function checkErrors() internal { 44 | // If the returned data begins with selector 'Error(string)', get the contained message - 45 | string memory message; 46 | bytes4 err_sel = bytes4(keccak256('Error(string)')); 47 | assembly { 48 | // Get pointer to free memory, place returned data at pointer, and update free memory pointer 49 | let ptr := mload(0x40) 50 | returndatacopy(ptr, 0, returndatasize) 51 | mstore(0x40, add(ptr, returndatasize)) 52 | 53 | // Check value at pointer for equality with Error selector - 54 | if eq(mload(ptr), and(err_sel, 0xffffffff00000000000000000000000000000000000000000000000000000000)) { 55 | message := add(0x24, ptr) 56 | } 57 | } 58 | // If no returned message exists, emit a default error message. Otherwise, emit the error message 59 | if (bytes(message).length == 0) 60 | emit StorageException(app_exec_id, "No error recieved"); 61 | else 62 | emit StorageException(app_exec_id, message); 63 | } 64 | 65 | // Returns the first 4 bytes of calldata 66 | function getSelector(bytes memory _calldata) internal pure returns (bytes4 selector) { 67 | assembly { 68 | selector := and( 69 | mload(add(0x20, _calldata)), 70 | 0xffffffff00000000000000000000000000000000000000000000000000000000 71 | ) 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /contracts/core/ScriptExec.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | import '../interfaces/StorageInterface.sol'; 4 | import '../interfaces/RegistryInterface.sol'; 5 | 6 | contract ScriptExec { 7 | 8 | /// DEFAULT VALUES /// 9 | 10 | address public app_storage; 11 | address public provider; 12 | bytes32 public registry_exec_id; 13 | address public exec_admin; 14 | 15 | /// APPLICATION INSTANCE METADATA /// 16 | 17 | struct Instance { 18 | address current_provider; 19 | bytes32 current_registry_exec_id; 20 | bytes32 app_exec_id; 21 | bytes32 app_name; 22 | bytes32 version_name; 23 | } 24 | 25 | // Maps the execution ids of deployed instances to the address that deployed them - 26 | mapping (bytes32 => address) public deployed_by; 27 | // Maps the execution ids of deployed instances to a struct containing their metadata - 28 | mapping (bytes32 => Instance) public instance_info; 29 | // Maps an address that deployed app instances to metadata about the deployed instance - 30 | mapping (address => Instance[]) public deployed_instances; 31 | // Maps an application name to the exec ids under which it is deployed - 32 | mapping (bytes32 => bytes32[]) public app_instances; 33 | 34 | /// EVENTS /// 35 | 36 | event AppInstanceCreated(address indexed creator, bytes32 indexed execution_id, bytes32 app_name, bytes32 version_name); 37 | event StorageException(bytes32 indexed execution_id, string message); 38 | 39 | // Modifier - The sender must be the contract administrator 40 | modifier onlyAdmin() { 41 | require(msg.sender == exec_admin); 42 | _; 43 | } 44 | 45 | // Payable function - for abstract storage refunds 46 | function () public payable { } 47 | 48 | /* 49 | Configure various defaults for a script exec contract 50 | @param _exec_admin: A privileged address, able to set the target provider and registry exec id 51 | @param _app_storage: The address to which applications will be stored 52 | @param _provider: The address under which applications have been initialized 53 | */ 54 | function configure(address _exec_admin, address _app_storage, address _provider) public { 55 | require(app_storage == 0, "ScriptExec already configured"); 56 | require(_app_storage != 0, 'Invalid input'); 57 | exec_admin = _exec_admin; 58 | app_storage = _app_storage; 59 | provider = _provider; 60 | 61 | if (exec_admin == 0) 62 | exec_admin = msg.sender; 63 | } 64 | 65 | /// APPLICATION EXECUTION /// 66 | 67 | bytes4 internal constant EXEC_SEL = bytes4(keccak256('exec(address,bytes32,bytes)')); 68 | 69 | /* 70 | Executes an application using its execution id and storage address. 71 | 72 | @param _exec_id: The instance exec id, which will route the calldata to the appropriate destination 73 | @param _calldata: The calldata to forward to the application 74 | @return success: Whether execution succeeded or not 75 | */ 76 | function exec(bytes32 _exec_id, bytes _calldata) external payable returns (bool success); 77 | 78 | bytes4 internal constant ERR = bytes4(keccak256('Error(string)')); 79 | 80 | // Return the bytes4 action requestor stored at the pointer, and cleans the remaining bytes 81 | function getAction(uint _ptr) internal pure returns (bytes4 action) { 82 | assembly { 83 | // Get the first 4 bytes stored at the pointer, and clean the rest of the bytes remaining 84 | action := and(mload(_ptr), 0xffffffff00000000000000000000000000000000000000000000000000000000) 85 | } 86 | } 87 | 88 | // Checks to see if an error message was returned with the failed call, and emits it if so - 89 | function checkErrors(bytes32 _exec_id) internal { 90 | // If the returned data begins with selector 'Error(string)', get the contained message - 91 | string memory message; 92 | bytes4 err_sel = ERR; 93 | assembly { 94 | // Get pointer to free memory, place returned data at pointer, and update free memory pointer 95 | let ptr := mload(0x40) 96 | returndatacopy(ptr, 0, returndatasize) 97 | mstore(0x40, add(ptr, returndatasize)) 98 | 99 | // Check value at pointer for equality with Error selector - 100 | if eq(mload(ptr), and(err_sel, 0xffffffff00000000000000000000000000000000000000000000000000000000)) { 101 | message := add(0x24, ptr) 102 | } 103 | } 104 | // If no returned message exists, emit a default error message. Otherwise, emit the error message 105 | if (bytes(message).length == 0) 106 | emit StorageException(_exec_id, "No error recieved"); 107 | else 108 | emit StorageException(_exec_id, message); 109 | } 110 | 111 | // Checks data returned by an application and returns whether or not the execution changed state 112 | function checkReturn() internal pure returns (bool success) { 113 | success = false; 114 | assembly { 115 | // returndata size must be 0x60 bytes 116 | if eq(returndatasize, 0x60) { 117 | // Copy returned data to pointer and check that at least one value is nonzero 118 | let ptr := mload(0x40) 119 | returndatacopy(ptr, 0, returndatasize) 120 | if iszero(iszero(mload(ptr))) { success := 1 } 121 | if iszero(iszero(mload(add(0x20, ptr)))) { success := 1 } 122 | if iszero(iszero(mload(add(0x40, ptr)))) { success := 1 } 123 | } 124 | } 125 | return success; 126 | } 127 | 128 | /// APPLICATION INITIALIZATION /// 129 | 130 | /* 131 | Initializes an instance of an application. Uses default app provider and registry app. 132 | Uses latest app version by default. 133 | @param _app_name: The name of the application to initialize 134 | @param _init_calldata: Calldata to be forwarded to the application's initialization function 135 | @return exec_id: The execution id (within the application's storage) of the created application instance 136 | @return version: The name of the version of the instance 137 | */ 138 | function createAppInstance(bytes32 _app_name, bytes _init_calldata) external returns (bytes32 exec_id, bytes32 version) { 139 | require(_app_name != 0 && _init_calldata.length >= 4, 'invalid input'); 140 | (exec_id, version) = StorageInterface(app_storage).createInstance( 141 | msg.sender, _app_name, provider, registry_exec_id, _init_calldata 142 | ); 143 | // Set various app metadata values - 144 | deployed_by[exec_id] = msg.sender; 145 | app_instances[_app_name].push(exec_id); 146 | Instance memory inst = Instance( 147 | provider, registry_exec_id, exec_id, _app_name, version 148 | ); 149 | instance_info[exec_id] = inst; 150 | deployed_instances[msg.sender].push(inst); 151 | // Emit event - 152 | emit AppInstanceCreated(msg.sender, exec_id, _app_name, version); 153 | } 154 | 155 | /// ADMIN FUNCTIONS /// 156 | 157 | /* 158 | Allows the exec admin to set the registry exec id from which applications will be initialized - 159 | @param _exec_id: The new exec id from which applications will be initialized 160 | */ 161 | function setRegistryExecID(bytes32 _exec_id) public onlyAdmin() { 162 | registry_exec_id = _exec_id; 163 | } 164 | 165 | /* 166 | Allows the exec admin to set the provider from which applications will be initialized in the given registry exec id 167 | @param _provider: The address under which applications to initialize are registered 168 | */ 169 | function setProvider(address _provider) public onlyAdmin() { 170 | provider = _provider; 171 | } 172 | 173 | // Allows the admin to set a new admin address 174 | function setAdmin(address _admin) public onlyAdmin() { 175 | require(_admin != 0); 176 | exec_admin = _admin; 177 | } 178 | 179 | /// STORAGE GETTERS /// 180 | 181 | // Returns a list of execution ids under which the given app name was deployed 182 | function getInstances(bytes32 _app_name) public view returns (bytes32[] memory) { 183 | return app_instances[_app_name]; 184 | } 185 | 186 | /* 187 | Returns the number of instances an address has created 188 | @param _deployer: The address that deployed the instances 189 | @return uint: The number of instances deployed by the deployer 190 | */ 191 | function getDeployedLength(address _deployer) public view returns (uint) { 192 | return deployed_instances[_deployer].length; 193 | } 194 | 195 | // The function selector for a simple registry 'registerApp' function 196 | bytes4 internal constant REGISTER_APP_SEL = bytes4(keccak256('registerApp(bytes32,address,bytes4[],address[])')); 197 | 198 | /* 199 | Returns the index address and implementing address for the simple registry app set as the default 200 | @return indx: The index address for the registry application - contains getters for the Registry, as well as its init funciton 201 | @return implementation: The address implementing the registry's functions 202 | */ 203 | function getRegistryImplementation() public view returns (address index, address implementation) { 204 | index = StorageInterface(app_storage).getIndex(registry_exec_id); 205 | implementation = StorageInterface(app_storage).getTarget(registry_exec_id, REGISTER_APP_SEL); 206 | } 207 | 208 | /* 209 | Returns the functions and addresses implementing those functions that make up an application under the give execution id 210 | @param _exec_id: The execution id that represents the application in storage 211 | @return index: The index address of the instance - holds the app's getter functions and init functions 212 | @return functions: A list of function selectors supported by the application 213 | @return implementations: A list of addresses corresponding to the function selectors, where those selectors are implemented 214 | */ 215 | function getInstanceImplementation(bytes32 _exec_id) public view 216 | returns (address index, bytes4[] memory functions, address[] memory implementations) { 217 | Instance memory app = instance_info[_exec_id]; 218 | index = StorageInterface(app_storage).getIndex(app.current_registry_exec_id); 219 | (index, functions, implementations) = RegistryInterface(index).getVersionImplementation( 220 | app_storage, app.current_registry_exec_id, app.current_provider, app.app_name, app.version_name 221 | ); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /contracts/interfaces/GetterInterface.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | interface GetterInterface { 4 | function read(bytes32 exec_id, bytes32 location) external view returns (bytes32 data); 5 | function readMulti(bytes32 exec_id, bytes32[] locations) external view returns (bytes32[] data); 6 | } 7 | -------------------------------------------------------------------------------- /contracts/interfaces/RegistryInterface.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | interface RegistryInterface { 4 | function getLatestVersion(address stor_addr, bytes32 exec_id, address provider, bytes32 app_name) 5 | external view returns (bytes32 latest_name); 6 | function getVersionImplementation(address stor_addr, bytes32 exec_id, address provider, bytes32 app_name, bytes32 version_name) 7 | external view returns (address index, bytes4[] selectors, address[] implementations); 8 | } 9 | -------------------------------------------------------------------------------- /contracts/interfaces/StorageInterface.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | interface StorageInterface { 4 | function getTarget(bytes32 exec_id, bytes4 selector) 5 | external view returns (address implementation); 6 | function getIndex(bytes32 exec_id) external view returns (address index); 7 | function createInstance(address sender, bytes32 app_name, address provider, bytes32 registry_exec_id, bytes calldata) 8 | external payable returns (bytes32 instance_exec_id, bytes32 version); 9 | function createRegistry(address index, address implementation) external returns (bytes32 exec_id); 10 | function exec(address sender, bytes32 exec_id, bytes calldata) 11 | external payable returns (uint emitted, uint paid, uint stored); 12 | } 13 | -------------------------------------------------------------------------------- /contracts/lib/ArrayUtils.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | library ArrayUtils { 4 | 5 | function toBytes4Arr(bytes32[] memory _arr) internal pure returns (bytes4[] memory _conv) { 6 | assembly { _conv := _arr } 7 | } 8 | 9 | function toAddressArr(bytes32[] memory _arr) internal pure returns (address[] memory _conv) { 10 | assembly { _conv := _arr } 11 | } 12 | 13 | function toUintArr(bytes32[] memory _arr) internal pure returns (uint[] memory _conv) { 14 | assembly { _conv := _arr } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /contracts/lib/SafeMath.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | library SafeMath { 4 | 5 | /** 6 | * @dev Multiplies two numbers, throws on overflow. 7 | */ 8 | function mul(uint256 a, uint256 b) internal pure returns (uint256 c) { 9 | if (a == 0) { 10 | return 0; 11 | } 12 | c = a * b; 13 | require(c / a == b, "Overflow - Multiplication"); 14 | return c; 15 | } 16 | 17 | /** 18 | * @dev Integer division of two numbers, truncating the quotient. 19 | */ 20 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 21 | return a / b; 22 | } 23 | 24 | /** 25 | * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). 26 | */ 27 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 28 | require(b <= a, "Underflow - Subtraction"); 29 | return a - b; 30 | } 31 | 32 | /** 33 | * @dev Adds two numbers, throws on overflow. 34 | */ 35 | function add(uint256 a, uint256 b) internal pure returns (uint256 c) { 36 | c = a + b; 37 | require(c >= a, "Overflow - Addition"); 38 | return c; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /contracts/lib/StringUtils.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | library StringUtils { 4 | 5 | function toStr(bytes32 _val) internal pure returns (string memory str) { 6 | assembly { 7 | str := mload(0x40) 8 | mstore(str, 0x20) 9 | mstore(add(0x20, str), _val) 10 | mstore(0x40, add(0x40, str)) 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /contracts/registry/RegistryExec.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | import '../core/ScriptExec.sol'; 4 | 5 | contract RegistryExec is ScriptExec { 6 | 7 | struct Registry { 8 | address index; 9 | address implementation; 10 | } 11 | 12 | // Maps execution ids to its registry app metadata 13 | mapping (bytes32 => Registry) public registry_instance_info; 14 | // Maps address to list of deployed Registry instances 15 | mapping (address => Registry[]) public deployed_registry_instances; 16 | 17 | /// EVENTS /// 18 | 19 | event RegistryInstanceCreated(address indexed creator, bytes32 indexed execution_id, address index, address implementation); 20 | 21 | /// APPLICATION EXECUTION /// 22 | 23 | bytes4 internal constant EXEC_SEL = bytes4(keccak256('exec(address,bytes32,bytes)')); 24 | 25 | /* 26 | Executes an application using its execution id and storage address. 27 | 28 | @param _exec_id: The instance exec id, which will route the calldata to the appropriate destination 29 | @param _calldata: The calldata to forward to the application 30 | @return success: Whether execution succeeded or not 31 | */ 32 | function exec(bytes32 _exec_id, bytes _calldata) external payable returns (bool success) { 33 | // Get function selector from calldata - 34 | bytes4 sel = getSelector(_calldata); 35 | // Ensure no registry functions are being called - 36 | require( 37 | sel != this.registerApp.selector && 38 | sel != this.registerAppVersion.selector && 39 | sel != UPDATE_INST_SEL && 40 | sel != UPDATE_EXEC_SEL 41 | ); 42 | 43 | // Call 'exec' in AbstractStorage, passing in the sender's address, the app exec id, and the calldata to forward - 44 | if (address(app_storage).call.value(msg.value)(abi.encodeWithSelector( 45 | EXEC_SEL, msg.sender, _exec_id, _calldata 46 | )) == false) { 47 | // Call failed - emit error message from storage and return 'false' 48 | checkErrors(_exec_id); 49 | // Return unspent wei to sender 50 | address(msg.sender).transfer(address(this).balance); 51 | return false; 52 | } 53 | 54 | // Get returned data 55 | success = checkReturn(); 56 | // If execution failed, 57 | require(success, 'Execution failed'); 58 | 59 | // Transfer any returned wei back to the sender 60 | address(msg.sender).transfer(address(this).balance); 61 | } 62 | 63 | // Returns the first 4 bytes of calldata 64 | function getSelector(bytes memory _calldata) internal pure returns (bytes4 selector) { 65 | assembly { 66 | selector := and( 67 | mload(add(0x20, _calldata)), 68 | 0xffffffff00000000000000000000000000000000000000000000000000000000 69 | ) 70 | } 71 | } 72 | 73 | /// REGISTRY FUNCTIONS /// 74 | 75 | /* 76 | Creates an instance of a registry application and returns its execution id 77 | @param _index: The index file of the registry app (holds getters and init functions) 78 | @param _implementation: The file implementing the registry's functionality 79 | @return exec_id: The execution id under which the registry will store data 80 | */ 81 | function createRegistryInstance(address _index, address _implementation) external onlyAdmin() returns (bytes32 exec_id) { 82 | // Validate input - 83 | require(_index != 0 && _implementation != 0, 'Invalid input'); 84 | 85 | // Creates a registry from storage and returns the registry exec id - 86 | exec_id = StorageInterface(app_storage).createRegistry(_index, _implementation); 87 | 88 | // Ensure a valid execution id returned from storage - 89 | require(exec_id != 0, 'Invalid response from storage'); 90 | 91 | // If there is not already a default registry exec id set, set it 92 | if (registry_exec_id == 0) 93 | registry_exec_id = exec_id; 94 | 95 | // Create Registry struct in memory - 96 | Registry memory reg = Registry(_index, _implementation); 97 | 98 | // Set various app metadata values - 99 | deployed_by[exec_id] = msg.sender; 100 | registry_instance_info[exec_id] = reg; 101 | deployed_registry_instances[msg.sender].push(reg); 102 | // Emit event - 103 | emit RegistryInstanceCreated(msg.sender, exec_id, _index, _implementation); 104 | } 105 | 106 | /* 107 | Registers an application as the admin under the provider and registry exec id 108 | @param _app_name: The name of the application to register 109 | @param _index: The index file of the application - holds the getters and init functions 110 | @param _selectors: The selectors of the functions which the app implements 111 | @param _implementations: The addresses at which each function is located 112 | */ 113 | function registerApp(bytes32 _app_name, address _index, bytes4[] _selectors, address[] _implementations) external onlyAdmin() { 114 | // Validate input 115 | require(_app_name != 0 && _index != 0, 'Invalid input'); 116 | require(_selectors.length == _implementations.length && _selectors.length != 0, 'Invalid input'); 117 | // Check contract variables for valid initialization 118 | require(app_storage != 0 && registry_exec_id != 0 && provider != 0, 'Invalid state'); 119 | 120 | // Execute registerApp through AbstractStorage - 121 | uint emitted; 122 | uint paid; 123 | uint stored; 124 | (emitted, paid, stored) = StorageInterface(app_storage).exec(msg.sender, registry_exec_id, msg.data); 125 | 126 | // Ensure zero values for emitted and paid, and nonzero value for stored - 127 | require(emitted == 0 && paid == 0 && stored != 0, 'Invalid state change'); 128 | } 129 | 130 | /* 131 | Registers a version of an application as the admin under the provider and registry exec id 132 | @param _app_name: The name of the application under which the version will be registered 133 | @param _version_name: The name of the version to register 134 | @param _index: The index file of the application - holds the getters and init functions 135 | @param _selectors: The selectors of the functions which the app implements 136 | @param _implementations: The addresses at which each function is located 137 | */ 138 | function registerAppVersion(bytes32 _app_name, bytes32 _version_name, address _index, bytes4[] _selectors, address[] _implementations) external onlyAdmin() { 139 | // Validate input 140 | require(_app_name != 0 && _version_name != 0 && _index != 0, 'Invalid input'); 141 | require(_selectors.length == _implementations.length && _selectors.length != 0, 'Invalid input'); 142 | // Check contract variables for valid initialization 143 | require(app_storage != 0 && registry_exec_id != 0 && provider != 0, 'Invalid state'); 144 | 145 | // Execute registerApp through AbstractStorage - 146 | uint emitted; 147 | uint paid; 148 | uint stored; 149 | (emitted, paid, stored) = StorageInterface(app_storage).exec(msg.sender, registry_exec_id, msg.data); 150 | 151 | // Ensure zero values for emitted and paid, and nonzero value for stored - 152 | require(emitted == 0 && paid == 0 && stored != 0, 'Invalid state change'); 153 | } 154 | 155 | // Update instance selectors, index, and addresses 156 | bytes4 internal constant UPDATE_INST_SEL = bytes4(keccak256('updateInstance(bytes32,bytes32,bytes32)')); 157 | 158 | /* 159 | Updates an application's implementations, selectors, and index address. Uses default app provider and registry app. 160 | Uses latest app version by default. 161 | 162 | @param _exec_id: The execution id of the application instance to be updated 163 | @return success: The success of the call to the application's updateInstance function 164 | */ 165 | function updateAppInstance(bytes32 _exec_id) external returns (bool success) { 166 | // Validate input. Only the original deployer can update an application - 167 | require(_exec_id != 0 && msg.sender == deployed_by[_exec_id], 'invalid sender or input'); 168 | 169 | // Get instance metadata from exec id - 170 | Instance memory inst = instance_info[_exec_id]; 171 | 172 | // Call 'exec' in AbstractStorage, passing in the sender's address, the execution id, and 173 | // the calldata to update the application - 174 | if(address(app_storage).call( 175 | abi.encodeWithSelector(EXEC_SEL, // 'exec' selector 176 | inst.current_provider, // application provider address 177 | _exec_id, // execution id to update 178 | abi.encodeWithSelector(UPDATE_INST_SEL, // calldata for Registry updateInstance function 179 | inst.app_name, // name of the applcation used by the instance 180 | inst.version_name, // name of the current version of the application 181 | inst.current_registry_exec_id // registry exec id when the instance was instantiated 182 | ) 183 | ) 184 | ) == false) { 185 | // Call failed - emit error message from storage and return 'false' 186 | checkErrors(_exec_id); 187 | return false; 188 | } 189 | // Check returned data to ensure state was correctly changed in AbstractStorage - 190 | success = checkReturn(); 191 | // If execution failed, revert state and return an error message - 192 | require(success, 'Execution failed'); 193 | 194 | // If execution was successful, the version was updated. Get the latest version 195 | // and set the exec id instance info - 196 | address registry_idx = StorageInterface(app_storage).getIndex(inst.current_registry_exec_id); 197 | bytes32 latest_version = RegistryInterface(registry_idx).getLatestVersion( 198 | app_storage, 199 | inst.current_registry_exec_id, 200 | inst.current_provider, 201 | inst.app_name 202 | ); 203 | // Ensure nonzero latest version - 204 | require(latest_version != 0, 'invalid latest version'); 205 | // Set current version - 206 | instance_info[_exec_id].version_name = latest_version; 207 | } 208 | 209 | // Update instance script exec contract 210 | bytes4 internal constant UPDATE_EXEC_SEL = bytes4(keccak256('updateExec(address)')); 211 | 212 | /* 213 | Updates an application's script executor from this Script Exec to a new address 214 | 215 | @param _exec_id: The execution id of the application instance to be updated 216 | @param _new_exec_addr: The new script exec address for this exec id 217 | @returns success: The success of the call to the application's updateExec function 218 | */ 219 | function updateAppExec(bytes32 _exec_id, address _new_exec_addr) external returns (bool success) { 220 | // Validate input. Only the original deployer can migrate the script exec address - 221 | require(_exec_id != 0 && msg.sender == deployed_by[_exec_id] && address(this) != _new_exec_addr && _new_exec_addr != 0, 'invalid input'); 222 | 223 | // Call 'exec' in AbstractStorage, passing in the sender's address, the execution id, and 224 | // the calldata to migrate the script exec address - 225 | if(address(app_storage).call( 226 | abi.encodeWithSelector(EXEC_SEL, // 'exec' selector 227 | msg.sender, // sender address 228 | _exec_id, // execution id to update 229 | abi.encodeWithSelector(UPDATE_EXEC_SEL, _new_exec_addr) // calldata for Registry updateExec 230 | ) 231 | ) == false) { 232 | // Call failed - emit error message from storage and return 'false' 233 | checkErrors(_exec_id); 234 | return false; 235 | } 236 | // Check returned data to ensure state was correctly changed in AbstractStorage - 237 | success = checkReturn(); 238 | // If execution failed, revert state and return an error message - 239 | require(success, 'Execution failed'); 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /contracts/registry/RegistryIdx.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | import '../core/Contract.sol'; 4 | import '../interfaces/GetterInterface.sol'; 5 | import '../lib/ArrayUtils.sol'; 6 | 7 | library RegistryIdx { 8 | 9 | using Contract for *; 10 | using ArrayUtils for bytes32[]; 11 | 12 | bytes32 internal constant EXEC_PERMISSIONS = keccak256('script_exec_permissions'); 13 | 14 | // Returns the storage location of a script execution address's permissions - 15 | function execPermissions(address _exec) internal pure returns (bytes32) 16 | { return keccak256(_exec, EXEC_PERMISSIONS); } 17 | 18 | // Simple init function - sets the sender as a script executor for this instance 19 | function init() external view { 20 | // Begin execution - we are initializing an instance of this application 21 | Contract.initialize(); 22 | // Begin storing init information - 23 | Contract.storing(); 24 | // Authorize sender as an executor for this instance - 25 | Contract.set(execPermissions(msg.sender)).to(true); 26 | // Finish storing and commit authorized sender to storage - 27 | Contract.commit(); 28 | } 29 | 30 | // Returns the location of a provider's list of registered applications in storage 31 | function registeredApps(address _provider) internal pure returns (bytes32) 32 | { return keccak256(bytes32(_provider), 'app_list'); } 33 | 34 | // Returns the location of a registered app's name under a provider 35 | function appBase(bytes32 _app, address _provider) internal pure returns (bytes32) 36 | { return keccak256(_app, keccak256(bytes32(_provider), 'app_base')); } 37 | 38 | // Returns the location of an app's list of versions 39 | function appVersionList(bytes32 _app, address _provider) internal pure returns (bytes32) 40 | { return keccak256('versions', appBase(_app, _provider)); } 41 | 42 | // Returns the location of a version's name 43 | function versionBase(bytes32 _app, bytes32 _version, address _provider) internal pure returns (bytes32) 44 | { return keccak256(_version, 'version', appBase(_app, _provider)); } 45 | 46 | // Returns the location of a registered app's index address under a provider 47 | function versionIndex(bytes32 _app, bytes32 _version, address _provider) internal pure returns (bytes32) 48 | { return keccak256('index', versionBase(_app, _version, _provider)); } 49 | 50 | // Returns the location of an app's function selectors, registered under a provider 51 | function versionSelectors(bytes32 _app, bytes32 _version, address _provider) internal pure returns (bytes32) 52 | { return keccak256('selectors', versionBase(_app, _version, _provider)); } 53 | 54 | // Returns the location of an app's implementing addresses, registered under a provider 55 | function versionAddresses(bytes32 _app, bytes32 _version, address _provider) internal pure returns (bytes32) 56 | { return keccak256('addresses', versionBase(_app, _version, _provider)); } 57 | 58 | // Return a list of applications registered by the address given 59 | function getApplications(address _storage, bytes32 _exec_id, address _provider) external view returns (bytes32[] memory) { 60 | uint seed = uint(registeredApps(_provider)); 61 | 62 | GetterInterface target = GetterInterface(_storage); 63 | uint length = uint(target.read(_exec_id, bytes32(seed))); 64 | 65 | bytes32[] memory arr_indices = new bytes32[](length); 66 | for (uint i = 1; i <= length; i++) 67 | arr_indices[i - 1] = bytes32((32 * i) + seed); 68 | 69 | return target.readMulti(_exec_id, arr_indices); 70 | } 71 | 72 | // Return a list of versions of an app registered by the maker 73 | function getVersions(address _storage, bytes32 _exec_id, address _provider, bytes32 _app) external view returns (bytes32[] memory) { 74 | uint seed = uint(appVersionList(_app, _provider)); 75 | 76 | GetterInterface target = GetterInterface(_storage); 77 | uint length = uint(target.read(_exec_id, bytes32(seed))); 78 | 79 | bytes32[] memory arr_indices = new bytes32[](length); 80 | for (uint i = 1; i <= length; i++) 81 | arr_indices[i - 1] = bytes32((32 * i) + seed); 82 | 83 | return target.readMulti(_exec_id, arr_indices); 84 | } 85 | 86 | // Returns the latest version of an application 87 | function getLatestVersion(address _storage, bytes32 _exec_id, address _provider, bytes32 _app) external view returns (bytes32) { 88 | uint seed = uint(appVersionList(_app, _provider)); 89 | 90 | GetterInterface target = GetterInterface(_storage); 91 | uint length = uint(target.read(_exec_id, bytes32(seed))); 92 | 93 | seed = (32 * length) + seed; 94 | 95 | return target.read(_exec_id, bytes32(seed)); 96 | } 97 | 98 | // Returns a version's index address, function selectors, and implementing addresses 99 | function getVersionImplementation(address _storage, bytes32 _exec_id, address _provider, bytes32 _app, bytes32 _version) external view 100 | returns (address index, bytes4[] memory selectors, address[] memory implementations) { 101 | uint seed = uint(versionIndex(_app, _version, _provider)); 102 | 103 | GetterInterface target = GetterInterface(_storage); 104 | index = address(target.read(_exec_id, bytes32(seed))); 105 | 106 | seed = uint(versionSelectors(_app, _version, _provider)); 107 | uint length = uint(target.read(_exec_id, bytes32(seed))); 108 | 109 | bytes32[] memory arr_indices = new bytes32[](length); 110 | for (uint i = 1; i <= length; i++) 111 | arr_indices[i - 1] = bytes32((32 * i) + seed); 112 | 113 | selectors = target.readMulti(_exec_id, arr_indices).toBytes4Arr(); 114 | 115 | seed = uint(versionAddresses(_app, _version, _provider)); 116 | for (i = 1; i <= length; i++) 117 | arr_indices[i - 1] = bytes32((32 * i) + seed); 118 | 119 | implementations = target.readMulti(_exec_id, arr_indices).toAddressArr(); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /contracts/registry/features/Provider.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | import '../../core/Contract.sol'; 4 | 5 | library Provider { 6 | 7 | using Contract for *; 8 | 9 | // Returns the index address for this exec id 10 | function appIndex() internal pure returns (bytes32) 11 | { return keccak256('index'); } 12 | 13 | // Storage seed for a script executor's execution permission mapping 14 | function execPermissions(address _exec) internal pure returns (bytes32) 15 | { return keccak256(_exec, keccak256('script_exec_permissions')); } 16 | 17 | // Storage seed for a function selector's implementation address 18 | function appSelectors(bytes4 _selector) internal pure returns (bytes32) 19 | { return keccak256(_selector, 'implementation'); } 20 | 21 | // Returns the location of a provider's list of registered applications in storage 22 | function registeredApps() internal pure returns (bytes32) 23 | { return keccak256(bytes32(Contract.sender()), 'app_list'); } 24 | 25 | // Returns the location of a registered app's name under a provider 26 | function appBase(bytes32 _app) internal pure returns (bytes32) 27 | { return keccak256(_app, keccak256(bytes32(Contract.sender()), 'app_base')); } 28 | 29 | // Returns the location of an app's list of versions 30 | function appVersionList(bytes32 _app) internal pure returns (bytes32) 31 | { return keccak256('versions', appBase(_app)); } 32 | 33 | // Returns the location of a version's name 34 | function versionBase(bytes32 _app, bytes32 _version) internal pure returns (bytes32) 35 | { return keccak256(_version, 'version', appBase(_app)); } 36 | 37 | // Returns the location of a registered app's index address under a provider 38 | function versionIndex(bytes32 _app, bytes32 _version) internal pure returns (bytes32) 39 | { return keccak256('index', versionBase(_app, _version)); } 40 | 41 | // Returns the location of an app's function selectors, registered under a provider 42 | function versionSelectors(bytes32 _app, bytes32 _version) internal pure returns (bytes32) 43 | { return keccak256('selectors', versionBase(_app, _version)); } 44 | 45 | // Returns the location of an app's implementing addresses, registered under a provider 46 | function versionAddresses(bytes32 _app, bytes32 _version) internal pure returns (bytes32) 47 | { return keccak256('addresses', versionBase(_app, _version)); } 48 | 49 | // Returns the location of the version before the current version 50 | function previousVersion(bytes32 _app, bytes32 _version) internal pure returns (bytes32) 51 | { return keccak256("previous version", versionBase(_app, _version)); } 52 | 53 | // Returns storage location of appversion list at a specific index 54 | function appVersionListAt(bytes32 _app, uint _index) internal pure returns (bytes32) 55 | { return bytes32((32 * _index) + uint(appVersionList(_app))); } 56 | 57 | // Registers an application under a given name for the sender 58 | function registerApp(bytes32 _app, address _index, bytes4[] _selectors, address[] _implementations) external view { 59 | // Begin execution - 60 | Contract.authorize(msg.sender); 61 | 62 | // Throw if the name has already been registered 63 | if (Contract.read(appBase(_app)) != bytes32(0)) 64 | revert("app is already registered"); 65 | 66 | if (_selectors.length != _implementations.length || _selectors.length == 0) 67 | revert("invalid input arrays"); 68 | 69 | // Start storing values 70 | Contract.storing(); 71 | 72 | // Store the app name in the list of registered app names 73 | uint num_registered_apps = uint(Contract.read(registeredApps())); 74 | 75 | Contract.increase(registeredApps()).by(uint(1)); 76 | 77 | Contract.set( 78 | bytes32(32 * (num_registered_apps + 1) + uint(registeredApps())) 79 | ).to(_app); 80 | 81 | // Store the app name at app_base 82 | Contract.set(appBase(_app)).to(_app); 83 | 84 | // Set the first version to this app 85 | Contract.set(versionBase(_app, _app)).to(_app); 86 | 87 | // Push the app to its own version list as the first version 88 | Contract.set(appVersionList(_app)).to(uint(1)); 89 | 90 | Contract.set( 91 | bytes32(32 + uint(appVersionList(_app))) 92 | ).to(_app); 93 | 94 | // Sets app index 95 | Contract.set(versionIndex(_app, _app)).to(_index); 96 | 97 | // Loop over the passed-in selectors and addresses and store them each at 98 | // version_selectors/version_addresses, respectively 99 | Contract.set(versionSelectors(_app, _app)).to(_selectors.length); 100 | Contract.set(versionAddresses(_app, _app)).to(_implementations.length); 101 | for (uint i = 0; i < _selectors.length; i++) { 102 | Contract.set(bytes32(32 * (i + 1) + uint(versionSelectors(_app, _app)))).to(_selectors[i]); 103 | Contract.set(bytes32(32 * (i + 1) + uint(versionAddresses(_app, _app)))).to(_implementations[i]); 104 | } 105 | 106 | // Set previous version to 0 107 | Contract.set(previousVersion(_app, _app)).to(uint(0)); 108 | 109 | // End execution and commit state changes to storage - 110 | Contract.commit(); 111 | } 112 | 113 | function registerAppVersion(bytes32 _app, bytes32 _version, address _index, bytes4[] _selectors, address[] _implementations) external view { 114 | // Begin execution - 115 | Contract.authorize(msg.sender); 116 | 117 | // Throw if the app has not been registered 118 | // Throw if the version has already been registered (check app_base) 119 | if (Contract.read(appBase(_app)) == bytes32(0)) 120 | revert("App has not been registered"); 121 | 122 | if (Contract.read(versionBase(_app, _version)) != bytes32(0)) 123 | revert("Version already exists"); 124 | 125 | if ( 126 | _selectors.length != _implementations.length || 127 | _selectors.length == 0 128 | ) revert("Invalid input array lengths"); 129 | 130 | // Begin storing values 131 | Contract.storing(); 132 | 133 | // Store the version name at version_base 134 | Contract.set(versionBase(_app, _version)).to(_version); 135 | 136 | // Push the version to the app's version list 137 | uint num_versions = uint(Contract.read(appVersionList(_app))); 138 | Contract.set(appVersionListAt(_app, (num_versions + 1))).to(_version); 139 | Contract.set(appVersionList(_app)).to(num_versions + 1); 140 | 141 | // Store the index at version_index 142 | Contract.set(versionIndex(_app, _version)).to(_index); 143 | 144 | // Loop over the passed-in selectors and addresses and store them each at 145 | // version_selectors/version_addresses, respectively 146 | Contract.set(versionSelectors(_app, _version)).to(_selectors.length); 147 | Contract.set(versionAddresses(_app, _version)).to(_implementations.length); 148 | for (uint i = 0; i < _selectors.length; i++) { 149 | Contract.set(bytes32(32 * (i + 1) + uint(versionSelectors(_app, _version)))).to(_selectors[i]); 150 | Contract.set(bytes32(32 * (i + 1) + uint(versionAddresses(_app, _version)))).to(_implementations[i]); 151 | } 152 | 153 | // Set the version's previous version 154 | bytes32 prev_version = Contract.read(bytes32(32 * num_versions + uint(appVersionList(_app)))); 155 | Contract.set(previousVersion(_app, _version)).to(prev_version); 156 | 157 | // End execution and commit state changes to storage - 158 | Contract.commit(); 159 | } 160 | 161 | /* 162 | Updates an application to the latest version - 163 | 164 | @param _provider: The provider of the application 165 | @param _app_name: The name of the application 166 | @param _current_version: The current version of the application 167 | @param _registry_id: The exec id of the registry of the application 168 | */ 169 | function updateInstance(bytes32 _app_name, bytes32 _current_version, bytes32 _registry_id) external view { 170 | // Begin execution - 171 | Contract.authorize(msg.sender); 172 | 173 | // Validate input - 174 | require(_app_name != 0 && _current_version != 0 && _registry_id != 0, 'invalid input'); 175 | 176 | // Get current version selectors and ensure nonzero length - 177 | bytes4[] memory current_selectors = getVersionSelectors(_app_name, _current_version, _registry_id); 178 | require(current_selectors.length != 0, 'invalid current version'); 179 | 180 | // Get latest version name and ensure it is not the current version, or zero - 181 | bytes32 latest_version = getLatestVersion(_app_name, _registry_id); 182 | require(latest_version != _current_version, 'current version is already latest'); 183 | require(latest_version != 0, 'invalid latest version'); 184 | 185 | // Get latest version index, selectors, and implementing addresses. 186 | // Ensure all returned values are valid - 187 | address latest_idx = getVersionIndex(_app_name, latest_version, _registry_id); 188 | bytes4[] memory latest_selectors = getVersionSelectors(_app_name, latest_version, _registry_id); 189 | address[] memory latest_impl = getVersionImplementations(_app_name, latest_version, _registry_id); 190 | require(latest_idx != 0, 'invalid version idx address'); 191 | require(latest_selectors.length != 0 && latest_selectors.length == latest_impl.length, 'invalid implementation specification'); 192 | 193 | // Set up a storage buffer to clear current version implementation - 194 | Contract.storing(); 195 | 196 | // For each selector, set its implementation to 0 197 | for (uint i = 0; i < current_selectors.length; i++) 198 | Contract.set(appSelectors(current_selectors[i])).to(address(0)); 199 | 200 | // Set this application's index address to equal the latest version's index - 201 | Contract.set(appIndex()).to(latest_idx); 202 | 203 | // Loop over implementing addresses, and map each function selector to its corresponding address for the new instance 204 | for (i = 0; i < latest_selectors.length; i++) { 205 | require(latest_selectors[i] != 0 && latest_impl[i] != 0, 'invalid input - expected nonzero implementation'); 206 | Contract.set(appSelectors(latest_selectors[i])).to(latest_impl[i]); 207 | } 208 | 209 | // Commit the changes to the storage contract 210 | Contract.commit(); 211 | } 212 | 213 | /* 214 | Replaces the script exec address with a new address 215 | 216 | @param _new_exec_addr: The address that will be granted permissions 217 | */ 218 | function updateExec(address _new_exec_addr) external view { 219 | // Authorize the sender and set up the run-time memory of this application 220 | Contract.authorize(msg.sender); 221 | 222 | // Validate input - 223 | require(_new_exec_addr != 0, 'invalid replacement'); 224 | 225 | // Set up a storage buffer - 226 | Contract.storing(); 227 | 228 | // Remove current permissions - 229 | Contract.set(execPermissions(msg.sender)).to(false); 230 | 231 | // Add updated permissions for the new address - 232 | Contract.set(execPermissions(_new_exec_addr)).to(true); 233 | 234 | // Commit the changes to the storage contract 235 | Contract.commit(); 236 | } 237 | 238 | /// Helpers /// 239 | 240 | function registryRead(bytes32 _location, bytes32 _registry_id) internal view returns (bytes32 value) { 241 | _location = keccak256(_location, _registry_id); 242 | assembly { value := sload(_location) } 243 | } 244 | 245 | /// Registry Getters /// 246 | 247 | /* 248 | Returns name of the latest version of an application 249 | 250 | @param _app: The name of the application 251 | @param _registry_id: The exec id of the registry application 252 | @return bytes32: The latest version of the application 253 | */ 254 | function getLatestVersion(bytes32 _app, bytes32 _registry_id) internal view returns (bytes32) { 255 | uint length = uint(registryRead(appVersionList(_app), _registry_id)); 256 | // Return the latest version of this application 257 | return registryRead(appVersionListAt(_app, length), _registry_id); 258 | } 259 | 260 | /* 261 | Returns the index address of an app version 262 | 263 | @param _app: The name of the application 264 | @param _version: The name of the version 265 | @param _registry_id: The exec id of the registry application 266 | @return address: The index address of this version 267 | */ 268 | function getVersionIndex(bytes32 _app, bytes32 _version, bytes32 _registry_id) internal view returns (address) { 269 | return address(registryRead(versionIndex(_app, _version), _registry_id)); 270 | } 271 | 272 | /* 273 | Returns the addresses associated with this version's implementation 274 | 275 | @param _app: The name of the application 276 | @param _version: The name of the version 277 | @param _registry_id: The exec id of the registry application 278 | @return impl: An address array containing all of this version's implementing addresses 279 | */ 280 | function getVersionImplementations(bytes32 _app, bytes32 _version, bytes32 _registry_id) internal view returns (address[] memory impl) { 281 | // Get number of addresses 282 | uint length = uint(registryRead(versionAddresses(_app, _version), _registry_id)); 283 | // Allocate space for return 284 | impl = new address[](length); 285 | // For each address, read it from storage and add it to the array 286 | for (uint i = 0; i < length; i++) { 287 | bytes32 location = bytes32(32 * (i + 1) + uint(versionAddresses(_app, _version))); 288 | impl[i] = address(registryRead(location, _registry_id)); 289 | } 290 | } 291 | 292 | /* 293 | Returns the function selectors associated with this version's implementation 294 | 295 | @param _app: The name of the application 296 | @param _version: The name of the version 297 | @param _registry_id: The exec id of the registry application 298 | @return sels: A bytes4 array containing all of this version's function selectors 299 | */ 300 | function getVersionSelectors(bytes32 _app, bytes32 _version, bytes32 _registry_id) internal view returns (bytes4[] memory sels) { 301 | // Get number of addresses 302 | uint length = uint(registryRead(versionSelectors(_app, _version), _registry_id)); 303 | // Allocate space for return 304 | sels = new bytes4[](length); 305 | // For each address, read it from storage and add it to the array 306 | for (uint i = 0; i < length; i++) { 307 | bytes32 location = bytes32(32 * (i + 1) + uint(versionSelectors(_app, _version))); 308 | sels[i] = bytes4(registryRead(location, _registry_id)); 309 | } 310 | } 311 | 312 | } 313 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "authos-solidity", 3 | "version": "1.1.1", 4 | "description": "Core contract library for auth-os", 5 | "files": [ 6 | "contracts", 7 | "Makefile", 8 | "test" 9 | ], 10 | "dependencies": { 11 | "chai": "^4.1.2", 12 | "chai-as-promised": "^7.1.1", 13 | "chai-bignumber": "^2.0.2", 14 | "coveralls": "^3.0.0", 15 | "ethereumjs-abi": "^0.6.5", 16 | "truffle": "^4.1.5", 17 | "web3": "^0.18.4", 18 | "mocha": "^3.5.3", 19 | "mochawesome": "^3.0.2", 20 | "mochawesome-report-generator": "^3.1.2" 21 | }, 22 | "devDependencies": {}, 23 | "scripts": { 24 | "test": "make test", 25 | "abi": "make abi", 26 | "clean": "make clean", 27 | "compile": "make compile", 28 | "flat": "make flat" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "git+https://github.com/auth-os/core.git" 33 | }, 34 | "keywords": [ 35 | "authos", 36 | "ethereum", 37 | "solidity", 38 | "evm", 39 | "smartcontracts", 40 | "kernel", 41 | "interoperability", 42 | "extensibility" 43 | ], 44 | "author": "Alexander Wade ", 45 | "co-author": "Kyle Thomas ", 46 | "contributors": [ 47 | "Alex Towle ", 48 | "Nikhil Sakhamuri " 49 | ], 50 | "license": "MIT", 51 | "bugs": { 52 | "url": "https://github.com/auth-os/core/issues" 53 | }, 54 | "homepage": "https://github.com/auth-os/core#readme" 55 | } 56 | -------------------------------------------------------------------------------- /test/fixtures/scenarios/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth-os/core/c73dffe9e8118906c18475d987564c5bd10108f6/test/fixtures/scenarios/.keep -------------------------------------------------------------------------------- /test/mock/AppInitMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | import './RevertHelper.sol'; 4 | 5 | library AppInitMock { 6 | 7 | bytes4 internal constant EMITS = bytes4(keccak256('Emit((bytes32[],bytes)[])')); 8 | bytes4 internal constant STORES = bytes4(keccak256('Store(bytes32[])')); 9 | bytes4 internal constant PAYS = bytes4(keccak256('Pay(bytes32[])')); 10 | bytes4 internal constant THROWS = bytes4(keccak256('Error(string)')); 11 | 12 | bytes32 internal constant EXEC_PERMISSIONS = keccak256('script_exec_permissions'); 13 | 14 | // Returns the storage location of a script execution address's permissions - 15 | function execPermissions(address _exec) internal pure returns (bytes32 location) { 16 | location = keccak256(_exec, EXEC_PERMISSIONS); 17 | } 18 | 19 | function init() external view { 20 | RevertHelper.revertBytes( 21 | abi.encodeWithSelector(STORES, uint(1), execPermissions(msg.sender), bytes32(1)) 22 | ); 23 | } 24 | 25 | function initInvalid() external pure { 26 | RevertHelper.revertBytes(new bytes(31)); 27 | } 28 | 29 | function initNullAction() external pure { 30 | RevertHelper.revertBytes(new bytes(36)); 31 | } 32 | 33 | function initThrowsAction() external pure { 34 | bytes memory temp = abi.encodeWithSelector(THROWS, uint(4)); 35 | RevertHelper.revertBytes(abi.encodePacked(temp, bytes4(0xffffffff))); 36 | } 37 | 38 | function initEmits(bytes32 _t1) external pure { 39 | RevertHelper.revertBytes(abi.encodeWithSelector(EMITS, uint(1), uint(1), _t1, uint(0))); 40 | } 41 | 42 | function initPays(address _dest) external view { 43 | RevertHelper.revertBytes(abi.encodeWithSelector(PAYS, uint(1), msg.value, _dest)); 44 | } 45 | 46 | function initStores(bytes32 _location, bytes32 _val) external pure { 47 | RevertHelper.revertBytes(abi.encodeWithSelector(STORES, uint(1), _location, _val)); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/mock/EmitsApp.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | import './RevertHelper.sol'; 4 | 5 | library EmitsApp { 6 | 7 | bytes4 internal constant EMITS = bytes4(keccak256('Emit((bytes32[],bytes)[])')); 8 | 9 | // emits 0 events 10 | function emit0() external pure { 11 | RevertHelper.revertBytes(abi.encodeWithSelector(EMITS, uint(0))); 12 | } 13 | 14 | // emits 1 event with 0 topics and no data 15 | function emit1top0() external pure { 16 | RevertHelper.revertBytes(abi.encodeWithSelector(EMITS, uint(1), uint(0), uint(0))); 17 | } 18 | 19 | // emits 1 event with 0 topics and with data 20 | function emit1top0data(bytes _data) external pure { 21 | bytes memory temp = abi.encodeWithSelector(EMITS, uint(1), uint(0)); 22 | RevertHelper.revertBytes(abi.encodePacked(temp, _data.length, _data)); 23 | } 24 | 25 | // emits 1 event with 4 topics and with data 26 | function emit1top4data(bytes32 _t1, bytes32 _t2, bytes32 _t3, bytes32 _t4, bytes _data) external pure { 27 | bytes memory temp = abi.encodeWithSelector( 28 | EMITS, uint(1), 29 | uint(4), _t1, _t2, _t3, _t4 30 | ); 31 | RevertHelper.revertBytes(abi.encodePacked(temp, _data.length, _data)); 32 | } 33 | 34 | // emits 2 events, each with 1 topic and data 35 | function emit2top1data(bytes32 _t1, bytes _data1, bytes _data2) external pure { 36 | bytes memory temp = abi.encodeWithSelector(EMITS, uint(2), uint(1), _t1); 37 | RevertHelper.revertBytes(abi.encodePacked( 38 | temp, _data1.length, _data1, 39 | uint(1), (1 + uint(_t1)), _data2.length, _data2 40 | )); 41 | } 42 | 43 | // emits 2 events, each with 4 topics and with no data 44 | function emit2top4(bytes32 _t1, bytes32 _t2, bytes32 _t3, bytes32 _t4) external pure { 45 | bytes memory temp = abi.encodeWithSelector( 46 | EMITS, uint(2), uint(4), _t1, _t2, _t3, _t4, uint(0) 47 | ); 48 | RevertHelper.revertBytes(abi.encodePacked( 49 | temp, uint(4), 50 | uint(_t1) + 1, uint(_t2) + 1, 51 | uint(_t3) + 1, uint(_t4) + 1, 52 | uint(0) 53 | )); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/mock/InvalidApp.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | import './RevertHelper.sol'; 4 | 5 | library InvalidApp { 6 | 7 | bytes4 internal constant EMITS = bytes4(keccak256('Emit((bytes32[],bytes)[])')); 8 | bytes4 internal constant STORES = bytes4(keccak256('Store(bytes32[])')); 9 | bytes4 internal constant PAYS = bytes4(keccak256('Pay(bytes32[])')); 10 | 11 | // attempts to pay the storage contract 12 | function inv1() external view { 13 | RevertHelper.revertBytes(abi.encodeWithSelector( 14 | PAYS, uint(1), uint(5), address(this) 15 | )); 16 | } 17 | 18 | // does not change state 19 | function inv2() external pure { 20 | RevertHelper.revertBytes(abi.encodeWithSelector( 21 | EMITS, uint(0), STORES, uint(0), PAYS, uint(0) 22 | )); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/mock/MixedApp.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | import './RevertHelper.sol'; 4 | 5 | library MixedApp { 6 | 7 | // ACTION REQUESTORS // 8 | 9 | bytes4 internal constant EMITS = bytes4(keccak256('Emit((bytes32[],bytes)[])')); 10 | bytes4 internal constant STORES = bytes4(keccak256('Store(bytes32[])')); 11 | bytes4 internal constant PAYS = bytes4(keccak256('Pay(bytes32[])')); 12 | bytes4 internal constant THROWS = bytes4(keccak256('Error(string)')); 13 | 14 | // EMITS 1, THROWS 15 | function req0(bytes32 _t1) external pure { 16 | bytes memory temp = abi.encodeWithSelector( 17 | EMITS, uint(1), uint(1), _t1, uint(0) 18 | ); 19 | RevertHelper.revertBytes(abi.encodePacked(temp, THROWS, uint(0))); 20 | } 21 | 22 | // PAYS 1, STORES 1 23 | function req1(address _dest, bytes32 _loc, bytes32 _val) external view { 24 | bytes memory temp = abi.encodeWithSelector( 25 | PAYS, uint(1), msg.value, _dest 26 | ); 27 | RevertHelper.revertBytes(abi.encodePacked(temp, STORES, uint(1), _loc, _val)); 28 | } 29 | 30 | // EMITS 1, STORES 1 31 | function req2(bytes32 _t1, bytes32 _loc, bytes32 _val) external pure { 32 | bytes memory temp = abi.encodeWithSelector( 33 | EMITS, uint(1), uint(1), uint(_t1), uint(0) 34 | ); 35 | RevertHelper.revertBytes(abi.encodePacked(temp, STORES, uint(1), _loc, _val)); 36 | } 37 | 38 | // PAYS 1, EMITS 1 39 | function req3(address _dest, bytes32 _t1) external view { 40 | bytes memory temp = abi.encodeWithSelector( 41 | PAYS, uint(1), msg.value, _dest 42 | ); 43 | RevertHelper.revertBytes(abi.encodePacked(temp, EMITS, uint(1), uint(1), _t1, uint(0))); 44 | } 45 | 46 | // PAYS 2, EMITS 1, THROWS 47 | function reqs0(address _dest1, address _dest2, bytes32 _t1, bytes _data) external view { 48 | bytes memory temp = abi.encodeWithSelector( 49 | PAYS, uint(2), (msg.value / 2), _dest1, (msg.value / 2), _dest2 50 | ); 51 | temp = abi.encodePacked( 52 | temp, EMITS, uint(1), uint(1), _t1, _data.length, _data 53 | ); 54 | RevertHelper.revertBytes(abi.encodePacked(temp, THROWS, uint(0))); 55 | } 56 | 57 | // EMITS 2, PAYS 1, STORES 2 58 | function reqs1( 59 | address _dest, bytes _data1, bytes _data2, bytes32 _loc1, bytes32 _val1, bytes32 _loc2, bytes32 _val2 60 | ) external view { 61 | bytes memory temp = abi.encodeWithSelector( 62 | EMITS, uint(2), uint(0) 63 | ); 64 | temp = abi.encodePacked(temp, _data1.length, _data1); 65 | temp = abi.encodePacked(temp, uint(0), _data2.length, _data2); 66 | temp = abi.encodePacked(temp, PAYS, uint(1), msg.value, bytes32(_dest)); 67 | RevertHelper.revertBytes(abi.encodePacked(temp, STORES, uint(2), _loc1, _val1, _loc2, _val2)); 68 | } 69 | 70 | // PAYS 1, EMITS 3, STORES 1 71 | function reqs2( 72 | address _dest, bytes32[4] _topics, bytes _data, bytes32 _loc, bytes32 _val1 73 | ) external view { 74 | bytes memory temp = abi.encodeWithSelector(PAYS, uint(1), msg.value, _dest); 75 | temp = abi.encodePacked( 76 | temp, EMITS, uint(3), _topics.length, _topics, _data.length, _data 77 | ); 78 | temp = abi.encodePacked( 79 | temp, _topics.length, 1 + uint( _topics[0]), 1 + uint( _topics[1]), 80 | 1 + uint( _topics[2]), 1 + uint( _topics[3]) 81 | ); 82 | temp = abi.encodePacked(temp, _data.length, _data); 83 | temp = abi.encodePacked( 84 | temp, _topics.length, 2 + uint(_topics[0]), 2 + uint(_topics[1]), 85 | 2 + uint(_topics[2]), 2 + uint(_topics[3]) 86 | ); 87 | temp = abi.encodePacked(temp, _data.length, _data); 88 | RevertHelper.revertBytes(abi.encodePacked(temp, STORES, uint(1), _loc, _val1)); 89 | } 90 | 91 | // STORES 2, PAYS 1, EMITS 1 92 | function reqs3( 93 | address _dest, bytes32 _t1, bytes _data, bytes32 _loc1, bytes32 _val1, bytes32 _loc2, bytes32 _val2 94 | ) external view { 95 | bytes memory temp = abi.encodeWithSelector( 96 | STORES, uint(2), _loc1, _val1, _loc2, _val2 97 | ); 98 | temp = abi.encodePacked(temp, PAYS, uint(1), msg.value, bytes32(_dest)); 99 | temp = abi.encodePacked(temp, EMITS, uint(1), uint(1), _t1); 100 | RevertHelper.revertBytes(abi.encodePacked(temp, _data.length, _data)); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /test/mock/PayableApp.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | import './RevertHelper.sol'; 4 | 5 | library PayableApp { 6 | 7 | bytes4 internal constant PAYS = bytes4(keccak256('Pay(bytes32[])')); 8 | 9 | // forwards payment to 0 addresses 10 | function pay0() external pure { 11 | RevertHelper.revertBytes(abi.encodeWithSelector(PAYS, uint(0))); 12 | } 13 | 14 | // forwards payment to one address 15 | function pay1(address _dest) external view { 16 | RevertHelper.revertBytes(abi.encodeWithSelector(PAYS, uint(1), (msg.value), _dest)); 17 | } 18 | 19 | // forwards payment to 2 addresses 20 | function pay2(address _dest1, address _dest2) external view { 21 | RevertHelper.revertBytes(abi.encodeWithSelector( 22 | PAYS, uint(2), (msg.value / 2), _dest1, (msg.value / 2), _dest2 23 | )); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/mock/RevertApp.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | library RevertApp { 4 | 5 | // Used to check errors when function does not exist 6 | /* function rev0() public pure { } */ 7 | 8 | function rev1() external pure { 9 | revert(); 10 | } 11 | 12 | function rev2() external pure { 13 | revert('message'); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/mock/RevertHelper.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | library RevertHelper { 4 | 5 | function revertBytes(bytes memory _in) internal pure { 6 | assembly { 7 | mstore(sub(_in, 0x20), 0x20) 8 | revert (sub(_in, 0x20), add(0x40, mload(_in))) 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/mock/StdApp.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | import './RevertHelper.sol'; 4 | 5 | library StdApp { 6 | 7 | bytes4 internal constant STORES = bytes4(keccak256('Store(bytes32[])')); 8 | 9 | // stores to 0 slots 10 | function std0() external pure { 11 | RevertHelper.revertBytes(abi.encodeWithSelector(STORES, uint(0))); 12 | } 13 | 14 | // stores to 1 slot 15 | function std1(bytes32 _location, bytes32 _val) external pure { 16 | RevertHelper.revertBytes(abi.encodeWithSelector(STORES, uint(1), _location, _val)); 17 | } 18 | 19 | // stores to 2 slots 20 | function std2( 21 | bytes32 _loc1, bytes32 _val1, bytes32 _loc2, bytes32 _val2 22 | ) external pure { 23 | RevertHelper.revertBytes(abi.encodeWithSelector( 24 | STORES, uint(2), _loc1, _val1, _loc2, _val2 25 | )); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/mock/application/functions/ApplicationMockFuncLib.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.21; 2 | 3 | 4 | library ApplicationMockFuncLib { 5 | 6 | function mockFn() public pure returns (bytes32[] store_data) { 7 | store_data = new bytes32[](4); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/mock/application/functions/init/ApplicationMockInit.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.21; 2 | 3 | 4 | library ApplicationMockInit { 5 | 6 | function init() public pure {} 7 | } 8 | -------------------------------------------------------------------------------- /test/mock/application/functions/init/ApplicationMockNonDefaultInit.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.21; 2 | 3 | 4 | library ApplicationMockNonDefaultInit { 5 | 6 | function init(bytes) public pure {} 7 | 8 | function initSel() public pure returns (bytes4) { 9 | return bytes4(keccak256("init(bytes)")); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/mock/application/functions/init/MockAppOne.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | library MockAppOne { 4 | 5 | function funcOneAppOne() public pure returns (bytes4) { 6 | return bytes4(0x65096456); 7 | } 8 | 9 | 10 | function funcTwoAppOne() public pure returns (bytes4) { 11 | return bytes4(0xc77e14f6); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/mock/application/functions/init/MockAppThree.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | library MockAppThree { 4 | 5 | function funcOneAppThree() public pure returns (bytes4) { 6 | return bytes4(0x0141b47e); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/mock/application/functions/init/MockAppTwo.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | library MockAppTwo { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /test/registry/RegistryExecMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | interface StorageInterface { 4 | function getTarget(bytes32 exec_id, bytes4 selector) 5 | external view returns (address implementation); 6 | function getIndex(bytes32 exec_id) external view returns (address index); 7 | function createInstance(address sender, bytes32 app_name, address provider, bytes32 registry_exec_id, bytes calldata) 8 | external payable returns (bytes32 instance_exec_id, bytes32 version); 9 | function updateInstance(address _sender, bytes32 _exec_id, bytes32 _app_name, address _provider, bytes32 _registry_id) external 10 | returns (bytes32 version); 11 | function updateExec(bytes32 exec_id, address new_exec) external; 12 | function createRegistry(address index, address implementation) external returns (bytes32 exec_id); 13 | function exec(address sender, bytes32 exec_id, bytes calldata) 14 | external payable returns (uint emitted, uint paid, uint stored); 15 | } 16 | 17 | interface RegistryInterface { 18 | function getLatestVersion(address stor_addr, bytes32 exec_id, address provider, bytes32 app_name) 19 | external view returns (bytes32 latest_name); 20 | function getVersionImplementation(address stor_addr, bytes32 exec_id, address provider, bytes32 app_name, bytes32 version_name) 21 | external view returns (address index, bytes4[] selectors, address[] implementations); 22 | } 23 | 24 | contract ScriptExecMock { 25 | 26 | /// DEFAULT VALUES /// 27 | 28 | address public app_storage; 29 | address public provider; 30 | bytes32 public registry_exec_id; 31 | address public exec_admin; 32 | 33 | /// APPLICATION INSTANCE METADATA /// 34 | 35 | struct Instance { 36 | address current_provider; 37 | bytes32 current_registry_exec_id; 38 | bytes32 app_exec_id; 39 | bytes32 app_name; 40 | bytes32 version_name; 41 | } 42 | 43 | // Maps the execution ids of deployed instances to the address that deployed them - 44 | mapping (bytes32 => address) public deployed_by; 45 | // Maps the execution ids of deployed instances to a struct containing their metadata - 46 | mapping (bytes32 => Instance) public instance_info; 47 | // Maps an address that deployed app instances to metadata about the deployed instance - 48 | mapping (address => Instance[]) public deployed_instances; 49 | // Maps an application name to the exec ids under which it is deployed - 50 | mapping (bytes32 => bytes32[]) public app_instances; 51 | 52 | /// EVENTS /// 53 | 54 | event AppInstanceCreated(address indexed creator, bytes32 indexed execution_id, bytes32 app_name, bytes32 version_name); 55 | event StorageException(bytes32 indexed execution_id, string message); 56 | 57 | // Modifier - The sender must be the contract administrator 58 | modifier onlyAdmin() { 59 | require(msg.sender == exec_admin); 60 | _; 61 | } 62 | 63 | // Payable function - for abstract storage refunds 64 | function () public payable { } 65 | 66 | /* 67 | Configure various defaults for a script exec contract 68 | @param _exec_admin: A privileged address, able to set the target provider and registry exec id 69 | @param _app_storage: The address to which applications will be stored 70 | @param _provider: The address under which applications have been initialized 71 | */ 72 | function configure(address _exec_admin, address _app_storage, address _provider) public { 73 | require(app_storage == 0, "ScriptExec already configured"); 74 | require(_app_storage != 0, 'Invalid input'); 75 | exec_admin = _exec_admin; 76 | app_storage = _app_storage; 77 | provider = _provider; 78 | 79 | if (exec_admin == 0) 80 | exec_admin = msg.sender; 81 | } 82 | 83 | /// APPLICATION EXECUTION /// 84 | 85 | bytes4 internal constant EXEC_SEL = bytes4(keccak256('exec(address,bytes32,bytes)')); 86 | 87 | /* 88 | Executes an application using its execution id and storage address. 89 | 90 | @param _exec_id: The instance exec id, which will route the calldata to the appropriate destination 91 | @param _calldata: The calldata to forward to the application 92 | @return success: Whether execution succeeded or not 93 | */ 94 | function exec(bytes32 _exec_id, bytes _calldata) external payable returns (bool success); 95 | 96 | bytes4 internal constant ERR = bytes4(keccak256('Error(string)')); 97 | 98 | // Return the bytes4 action requestor stored at the pointer, and cleans the remaining bytes 99 | function getAction(uint _ptr) internal pure returns (bytes4 action) { 100 | assembly { 101 | // Get the first 4 bytes stored at the pointer, and clean the rest of the bytes remaining 102 | action := and(mload(_ptr), 0xffffffff00000000000000000000000000000000000000000000000000000000) 103 | } 104 | } 105 | 106 | // Checks to see if an error message was returned with the failed call, and emits it if so - 107 | function checkErrors(bytes32 _exec_id) internal { 108 | // If the returned data begins with selector 'Error(string)', get the contained message - 109 | string memory message; 110 | bytes4 err_sel = ERR; 111 | assembly { 112 | // Get pointer to free memory, place returned data at pointer, and update free memory pointer 113 | let ptr := mload(0x40) 114 | returndatacopy(ptr, 0, returndatasize) 115 | mstore(0x40, add(ptr, returndatasize)) 116 | 117 | // Check value at pointer for equality with Error selector - 118 | if eq(mload(ptr), and(err_sel, 0xffffffff00000000000000000000000000000000000000000000000000000000)) { 119 | message := add(0x24, ptr) 120 | } 121 | } 122 | // If no returned message exists, emit a default error message. Otherwise, emit the error message 123 | if (bytes(message).length == 0) 124 | emit StorageException(_exec_id, "No error recieved"); 125 | else 126 | emit StorageException(_exec_id, message); 127 | } 128 | 129 | // Checks data returned by an application and returns whether or not the execution changed state 130 | function checkReturn() internal pure returns (bool success) { 131 | success = false; 132 | assembly { 133 | // returndata size must be 0x60 bytes 134 | if eq(returndatasize, 0x60) { 135 | // Copy returned data to pointer and check that at least one value is nonzero 136 | let ptr := mload(0x40) 137 | returndatacopy(ptr, 0, returndatasize) 138 | if iszero(iszero(mload(ptr))) { success := 1 } 139 | if iszero(iszero(mload(add(0x20, ptr)))) { success := 1 } 140 | if iszero(iszero(mload(add(0x40, ptr)))) { success := 1 } 141 | } 142 | } 143 | return success; 144 | } 145 | 146 | /// APPLICATION INITIALIZATION /// 147 | 148 | /* 149 | Initializes an instance of an application. Uses default app provider and registry app. 150 | Uses latest app version by default. 151 | @param _app_name: The name of the application to initialize 152 | @param _init_calldata: Calldata to be forwarded to the application's initialization function 153 | @return exec_id: The execution id (within the application's storage) of the created application instance 154 | @return version: The name of the version of the instance 155 | */ 156 | function createAppInstance(bytes32 _app_name, bytes _init_calldata) external returns (bytes32 exec_id, bytes32 version) { 157 | require(_app_name != 0 && _init_calldata.length >= 4, 'invalid input'); 158 | (exec_id, version) = StorageInterface(app_storage).createInstance( 159 | msg.sender, _app_name, provider, registry_exec_id, _init_calldata 160 | ); 161 | // Set various app metadata values - 162 | deployed_by[exec_id] = msg.sender; 163 | app_instances[_app_name].push(exec_id); 164 | Instance memory inst = Instance( 165 | provider, registry_exec_id, exec_id, _app_name, version 166 | ); 167 | instance_info[exec_id] = inst; 168 | deployed_instances[msg.sender].push(inst); 169 | // Emit event - 170 | emit AppInstanceCreated(msg.sender, exec_id, _app_name, version); 171 | } 172 | 173 | /// ADMIN FUNCTIONS /// 174 | 175 | /* 176 | Allows the exec admin to set the registry exec id from which applications will be initialized - 177 | @param _exec_id: The new exec id from which applications will be initialized 178 | */ 179 | function setRegistryExecID(bytes32 _exec_id) public onlyAdmin() { 180 | registry_exec_id = _exec_id; 181 | } 182 | 183 | /* 184 | Allows the exec admin to set the provider from which applications will be initialized in the given registry exec id 185 | @param _provider: The address under which applications to initialize are registered 186 | */ 187 | function setProvider(address _provider) public onlyAdmin() { 188 | provider = _provider; 189 | } 190 | 191 | // Allows the admin to set a new admin address 192 | function setAdmin(address _admin) public onlyAdmin() { 193 | require(_admin != 0); 194 | exec_admin = _admin; 195 | } 196 | 197 | /// STORAGE GETTERS /// 198 | 199 | // Returns a list of execution ids under which the given app name was deployed 200 | function getInstances(bytes32 _app_name) public view returns (bytes32[] memory) { 201 | return app_instances[_app_name]; 202 | } 203 | 204 | /* 205 | Returns the number of instances an address has created 206 | @param _deployer: The address that deployed the instances 207 | @return uint: The number of instances deployed by the deployer 208 | */ 209 | function getDeployedLength(address _deployer) public view returns (uint) { 210 | return deployed_instances[_deployer].length; 211 | } 212 | 213 | // The function selector for a simple registry 'registerApp' function 214 | bytes4 internal constant REGISTER_APP_SEL = bytes4(keccak256('registerApp(bytes32,address,bytes4[],address[])')); 215 | 216 | /* 217 | Returns the index address and implementing address for the simple registry app set as the default 218 | @return indx: The index address for the registry application - contains getters for the Registry, as well as its init funciton 219 | @return implementation: The address implementing the registry's functions 220 | */ 221 | function getRegistryImplementation() public view returns (address index, address implementation) { 222 | index = StorageInterface(app_storage).getIndex(registry_exec_id); 223 | implementation = StorageInterface(app_storage).getTarget(registry_exec_id, REGISTER_APP_SEL); 224 | } 225 | 226 | /* 227 | Returns the functions and addresses implementing those functions that make up an application under the give execution id 228 | @param _exec_id: The execution id that represents the application in storage 229 | @return index: The index address of the instance - holds the app's getter functions and init functions 230 | @return functions: A list of function selectors supported by the application 231 | @return implementations: A list of addresses corresponding to the function selectors, where those selectors are implemented 232 | */ 233 | function getInstanceImplementation(bytes32 _exec_id) public view 234 | returns (address index, bytes4[] memory functions, address[] memory implementations) { 235 | Instance memory app = instance_info[_exec_id]; 236 | index = StorageInterface(app_storage).getIndex(app.current_registry_exec_id); 237 | (index, functions, implementations) = RegistryInterface(index).getVersionImplementation( 238 | app_storage, app.current_registry_exec_id, app.current_provider, app.app_name, app.version_name 239 | ); 240 | } 241 | } 242 | 243 | contract RegistryExecMock is ScriptExecMock { 244 | 245 | struct Registry { 246 | address index; 247 | address implementation; 248 | } 249 | 250 | // Maps execution ids to its registry app metadata 251 | mapping (bytes32 => Registry) public registry_instance_info; 252 | // Maps address to list of deployed Registry instances 253 | mapping (address => Registry[]) public deployed_registry_instances; 254 | 255 | /// EVENTS /// 256 | 257 | event RegistryInstanceCreated(address indexed creator, bytes32 indexed execution_id, address index, address implementation); 258 | 259 | /// APPLICATION EXECUTION /// 260 | 261 | bytes4 internal constant EXEC_SEL = bytes4(keccak256('exec(address,bytes32,bytes)')); 262 | 263 | /* 264 | Executes an application using its execution id and storage address. 265 | 266 | @param _exec_id: The instance exec id, which will route the calldata to the appropriate destination 267 | @param _calldata: The calldata to forward to the application 268 | @return success: Whether execution succeeded or not 269 | */ 270 | function exec(bytes32 _exec_id, bytes _calldata) external payable returns (bool success) { 271 | // Get function selector from calldata - 272 | bytes4 sel = getSelector(_calldata); 273 | // Ensure no registry functions are being called - 274 | require( 275 | sel != this.registerApp.selector && 276 | sel != this.registerAppVersion.selector && 277 | sel != UPDATE_INST_SEL && 278 | sel != UPDATE_EXEC_SEL 279 | ); 280 | 281 | // Call 'exec' in AbstractStorage, passing in the sender's address, the app exec id, and the calldata to forward - 282 | if (address(app_storage).call.value(msg.value)(abi.encodeWithSelector( 283 | EXEC_SEL, msg.sender, _exec_id, _calldata 284 | )) == false) { 285 | // Call failed - emit error message from storage and return 'false' 286 | checkErrors(_exec_id); 287 | // Return unspent wei to sender 288 | address(msg.sender).transfer(address(this).balance); 289 | return false; 290 | } 291 | 292 | // Get returned data 293 | success = checkReturn(); 294 | // If execution failed, 295 | require(success, 'Execution failed'); 296 | 297 | // Transfer any returned wei back to the sender 298 | address(msg.sender).transfer(address(this).balance); 299 | } 300 | 301 | // Returns the first 4 bytes of calldata 302 | function getSelector(bytes memory _calldata) internal pure returns (bytes4 selector) { 303 | assembly { 304 | selector := and( 305 | mload(add(0x20, _calldata)), 306 | 0xffffffff00000000000000000000000000000000000000000000000000000000 307 | ) 308 | } 309 | } 310 | 311 | /// REGISTRY FUNCTIONS /// 312 | 313 | /* 314 | Creates an instance of a registry application and returns its execution id 315 | @param _index: The index file of the registry app (holds getters and init functions) 316 | @param _implementation: The file implementing the registry's functionality 317 | @return exec_id: The execution id under which the registry will store data 318 | */ 319 | function createRegistryInstance(address _index, address _implementation) external onlyAdmin() returns (bytes32 exec_id) { 320 | // Validate input - 321 | require(_index != 0 && _implementation != 0, 'Invalid input'); 322 | 323 | // Creates a registry from storage and returns the registry exec id - 324 | exec_id = StorageInterface(app_storage).createRegistry(_index, _implementation); 325 | 326 | // Ensure a valid execution id returned from storage - 327 | require(exec_id != 0, 'Invalid response from storage'); 328 | 329 | // If there is not already a default registry exec id set, set it 330 | if (registry_exec_id == 0) 331 | registry_exec_id = exec_id; 332 | 333 | // Create Registry struct in memory - 334 | Registry memory reg = Registry(_index, _implementation); 335 | 336 | // Set various app metadata values - 337 | deployed_by[exec_id] = msg.sender; 338 | registry_instance_info[exec_id] = reg; 339 | deployed_registry_instances[msg.sender].push(reg); 340 | // Emit event - 341 | emit RegistryInstanceCreated(msg.sender, exec_id, _index, _implementation); 342 | } 343 | 344 | /* 345 | Registers an application as the admin under the provider and registry exec id 346 | @param _app_name: The name of the application to register 347 | @param _index: The index file of the application - holds the getters and init functions 348 | @param _selectors: The selectors of the functions which the app implements 349 | @param _implementations: The addresses at which each function is located 350 | */ 351 | function registerApp(bytes32 _app_name, address _index, bytes4[] _selectors, address[] _implementations) external onlyAdmin() { 352 | // Validate input 353 | require(_app_name != 0 && _index != 0, 'Invalid input'); 354 | require(_selectors.length == _implementations.length && _selectors.length != 0, 'Invalid input'); 355 | // Check contract variables for valid initialization 356 | require(app_storage != 0 && registry_exec_id != 0 && provider != 0, 'Invalid state'); 357 | 358 | // Execute registerApp through AbstractStorage - 359 | uint emitted; 360 | uint paid; 361 | uint stored; 362 | (emitted, paid, stored) = StorageInterface(app_storage).exec(msg.sender, registry_exec_id, msg.data); 363 | 364 | // Ensure zero values for emitted and paid, and nonzero value for stored - 365 | require(emitted == 0 && paid == 0 && stored != 0, 'Invalid state change'); 366 | } 367 | 368 | /* 369 | Registers a version of an application as the admin under the provider and registry exec id 370 | @param _app_name: The name of the application under which the version will be registered 371 | @param _version_name: The name of the version to register 372 | @param _index: The index file of the application - holds the getters and init functions 373 | @param _selectors: The selectors of the functions which the app implements 374 | @param _implementations: The addresses at which each function is located 375 | */ 376 | function registerAppVersion(bytes32 _app_name, bytes32 _version_name, address _index, bytes4[] _selectors, address[] _implementations) external onlyAdmin() { 377 | // Validate input 378 | require(_app_name != 0 && _version_name != 0 && _index != 0, 'Invalid input'); 379 | require(_selectors.length == _implementations.length && _selectors.length != 0, 'Invalid input'); 380 | // Check contract variables for valid initialization 381 | require(app_storage != 0 && registry_exec_id != 0 && provider != 0, 'Invalid state'); 382 | 383 | // Execute registerApp through AbstractStorage - 384 | uint emitted; 385 | uint paid; 386 | uint stored; 387 | (emitted, paid, stored) = StorageInterface(app_storage).exec(msg.sender, registry_exec_id, msg.data); 388 | 389 | // Ensure zero values for emitted and paid, and nonzero value for stored - 390 | require(emitted == 0 && paid == 0 && stored != 0, 'Invalid state change'); 391 | } 392 | 393 | // Update instance selectors, index, and addresses 394 | bytes4 internal constant UPDATE_INST_SEL = bytes4(keccak256('updateInstance(bytes32,bytes32,bytes32)')); 395 | 396 | /* 397 | Updates an application's implementations, selectors, and index address. Uses default app provider and registry app. 398 | Uses latest app version by default. 399 | 400 | @param _exec_id: The execution id of the application instance to be updated 401 | @return success: The success of the call to the application's updateInstance function 402 | */ 403 | function updateAppInstance(bytes32 _exec_id) external returns (bool success) { 404 | // Validate input. Only the original deployer can update an application - 405 | require(_exec_id != 0 && msg.sender == deployed_by[_exec_id], 'invalid sender or input'); 406 | 407 | // Get instance metadata from exec id - 408 | Instance memory inst = instance_info[_exec_id]; 409 | 410 | // Call 'exec' in AbstractStorage, passing in the sender's address, the execution id, and 411 | // the calldata to update the application - 412 | if(address(app_storage).call( 413 | abi.encodeWithSelector(EXEC_SEL, // 'exec' selector 414 | inst.current_provider, // application provider address 415 | _exec_id, // execution id to update 416 | abi.encodeWithSelector(UPDATE_INST_SEL, // calldata for Registry updateInstance function 417 | inst.app_name, // name of the applcation used by the instance 418 | inst.version_name, // name of the current version of the application 419 | inst.current_registry_exec_id // registry exec id when the instance was instantiated 420 | ) 421 | ) 422 | ) == false) { 423 | // Call failed - emit error message from storage and return 'false' 424 | checkErrors(_exec_id); 425 | return false; 426 | } 427 | // Check returned data to ensure state was correctly changed in AbstractStorage - 428 | success = checkReturn(); 429 | // If execution failed, revert state and return an error message - 430 | require(success, 'Execution failed'); 431 | 432 | // If execution was successful, the version was updated. Get the latest version 433 | // and set the exec id instance info - 434 | address registry_idx = StorageInterface(app_storage).getIndex(inst.current_registry_exec_id); 435 | bytes32 latest_version = RegistryInterface(registry_idx).getLatestVersion( 436 | app_storage, 437 | inst.current_registry_exec_id, 438 | inst.current_provider, 439 | inst.app_name 440 | ); 441 | // Ensure nonzero latest version - 442 | require(latest_version != 0, 'invalid latest version'); 443 | // Set current version - 444 | instance_info[_exec_id].version_name = latest_version; 445 | } 446 | 447 | // Update instance script exec contract 448 | bytes4 internal constant UPDATE_EXEC_SEL = bytes4(keccak256('updateExec(address)')); 449 | 450 | /* 451 | Updates an application's script executor from this Script Exec to a new address 452 | 453 | @param _exec_id: The execution id of the application instance to be updated 454 | @param _new_exec_addr: The new script exec address for this exec id 455 | @returns success: The success of the call to the application's updateExec function 456 | */ 457 | function updateAppExec(bytes32 _exec_id, address _new_exec_addr) external returns (bool success) { 458 | // Call 'exec' in AbstractStorage, passing in the sender's address, the execution id, and 459 | // the calldata to migrate the script exec address - 460 | if(address(app_storage).call( 461 | abi.encodeWithSelector(EXEC_SEL, // 'exec' selector 462 | msg.sender, // sender address 463 | _exec_id, // execution id to update 464 | abi.encodeWithSelector(UPDATE_EXEC_SEL, _new_exec_addr) // calldata for Registry updateExec 465 | ) 466 | ) == false) { 467 | // Call failed - emit error message from storage and return 'false' 468 | checkErrors(_exec_id); 469 | return false; 470 | } 471 | // Check returned data to ensure state was correctly changed in AbstractStorage - 472 | success = checkReturn(); 473 | // If execution failed, revert state and return an error message - 474 | require(success, 'Execution failed'); 475 | } 476 | } 477 | -------------------------------------------------------------------------------- /test/support/chai.js: -------------------------------------------------------------------------------- 1 | require('chai') 2 | .use(require('chai-as-promised')) 3 | .use(require('chai-bignumber')(web3.BigNumber)) 4 | .should(); 5 | -------------------------------------------------------------------------------- /test/support/evm.js: -------------------------------------------------------------------------------- 1 | exports.EVM_ERR_REVERT = 'VM Exception while processing transaction: revert' 2 | -------------------------------------------------------------------------------- /test/support/utils.js: -------------------------------------------------------------------------------- 1 | const ADDRESS_0x = exports.ADDRESS_0x = '0x0000000000000000000000000000000000000000' 2 | const BYTES32_EMPTY = exports.BYTES32_EMPTY = '0x0000000000000000000000000000000000000000000000000000000000000000' 3 | 4 | const randomBytes = exports.randomBytes = (len) => { 5 | var str = '' 6 | while (str.length < len) { 7 | str += Math.random().toString(36).substring(0, 12).substring(2) 8 | } 9 | return str.substring(0, len) 10 | } 11 | -------------------------------------------------------------------------------- /test/util/AppInitUtil.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | contract AppInitUtil { 4 | 5 | function init() public pure returns (bytes memory) { return msg.data; } 6 | 7 | function initInvalid() public pure returns (bytes memory) { return msg.data; } 8 | 9 | function initNullAction() public pure returns (bytes memory) { return msg.data; } 10 | 11 | function initThrowsAction() public pure returns (bytes memory) { return msg.data; } 12 | 13 | function initEmits(bytes32) public pure returns (bytes memory) { return msg.data; } 14 | 15 | function initPays(address) public pure returns (bytes memory) { return msg.data; } 16 | 17 | function initStores(bytes32, bytes32) public pure returns (bytes memory) { return msg.data; } 18 | 19 | function parseInit(bytes memory _data) public pure returns (address exec, address updater) { 20 | assembly { 21 | exec := mload(add(0x20, _data)) 22 | updater := mload(add(0x40, _data)) 23 | } 24 | } 25 | 26 | function parseInstanceCreated(bytes memory _data) public pure returns (address storage_addr, bytes32 app, bytes32 version) { 27 | assembly { 28 | storage_addr := mload(add(0x20, _data)) 29 | app := mload(add(0x40, _data)) 30 | version := mload(add(0x60, _data)) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/util/AppMockUtil.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | contract AppMockUtil { 4 | 5 | function getSelectors() public pure returns (bytes4[] memory selectors) { 6 | selectors = new bytes4[](27); 7 | // pay 8 | selectors[0] = this.pay0.selector; 9 | selectors[1] = this.pay1.selector; 10 | selectors[2] = this.pay2.selector; 11 | // std 12 | selectors[3] = this.std0.selector; 13 | selectors[4] = this.std1.selector; 14 | selectors[5] = this.std2.selector; 15 | // emit 16 | selectors[6] = this.emit0.selector; 17 | selectors[7] = this.emit1top0.selector; 18 | selectors[8] = this.emit1top0data.selector; 19 | selectors[9] = this.emit1top4data.selector; 20 | selectors[10] = this.emit2top1data.selector; 21 | selectors[11] = this.emit2top4.selector; 22 | // mix 23 | selectors[12] = this.req0.selector; 24 | selectors[13] = this.req1.selector; 25 | selectors[14] = this.req2.selector; 26 | selectors[15] = this.req3.selector; 27 | selectors[16] = this.reqs0.selector; 28 | selectors[17] = this.reqs1.selector; 29 | selectors[18] = this.reqs2.selector; 30 | selectors[19] = this.reqs3.selector; 31 | // inv 32 | selectors[20] = this.inv1.selector; 33 | selectors[21] = this.inv2.selector; 34 | // rev 35 | selectors[22] = this.rev0.selector; 36 | selectors[23] = this.rev1.selector; 37 | selectors[24] = this.rev2.selector; 38 | // update 39 | selectors[25] = this.updateInstance.selector; 40 | selectors[26] = this.updateExec.selector; 41 | } 42 | 43 | /// PAYABLE APP /// 44 | 45 | function pay0() external pure returns (bytes) { return msg.data; } 46 | function pay1(address) external pure returns (bytes) { return msg.data; } 47 | function pay2(address, address) external pure returns (bytes) { return msg.data; } 48 | 49 | /// STD APP /// 50 | 51 | function std0() external pure returns (bytes) { return msg.data; } 52 | function std1(bytes32, bytes32) external pure returns (bytes) { return msg.data; } 53 | function std2(bytes32, bytes32, bytes32, bytes32) external pure returns (bytes) { return msg.data; } 54 | 55 | /// EMITS APP /// 56 | 57 | function emit0() external pure returns (bytes) { return msg.data; } 58 | function emit1top0() external pure returns (bytes) { return msg.data; } 59 | function emit1top0data(bytes) external pure returns (bytes) { return msg.data; } 60 | function emit1top4data(bytes32, bytes32, bytes32, bytes32, bytes) external pure returns (bytes) { return msg.data; } 61 | function emit2top1data(bytes32, bytes, bytes) external pure returns (bytes) { return msg.data; } 62 | function emit2top4(bytes32, bytes32, bytes32, bytes32) external pure returns (bytes) { return msg.data; } 63 | 64 | /// MIXED APP /// 65 | 66 | function req0(bytes32) external pure returns (bytes) { return msg.data; } 67 | function req1(address, bytes32, bytes32) external pure returns (bytes) { return msg.data; } 68 | function req2(bytes32, bytes32, bytes32) external pure returns (bytes) { return msg.data; } 69 | function req3(address, bytes32) external pure returns (bytes) { return msg.data; } 70 | function reqs0(address, address, bytes32, bytes) external pure returns (bytes) { return msg.data; } 71 | function reqs1( 72 | address, bytes, bytes, bytes32, bytes32, bytes32, bytes32 73 | ) external pure returns (bytes) { return msg.data; } 74 | function reqs2( 75 | address, bytes32[4], bytes, bytes32, bytes32 76 | ) external pure returns (bytes) { return msg.data; } 77 | function reqs3( 78 | address, bytes32, bytes, bytes32, bytes32, bytes32, bytes32 79 | ) external pure returns (bytes) { return msg.data; } 80 | 81 | /// INVALID APP /// 82 | 83 | function inv1() external pure returns (bytes) { return msg.data; } 84 | function inv2() external pure returns (bytes) { return msg.data; } 85 | 86 | /// REVERT APP /// 87 | 88 | function rev0() external pure returns (bytes) { return msg.data; } 89 | function rev1() external pure returns (bytes) { return msg.data; } 90 | function rev2() external pure returns (bytes) { return msg.data; } 91 | 92 | /// UPDATES /// 93 | 94 | function updateInstance(bytes32, bytes32, bytes32) external pure returns (bytes) { return msg.data; } 95 | function updateExec(address) external pure returns (bytes) { return msg.data; } 96 | } 97 | -------------------------------------------------------------------------------- /test/util/TestUtils.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | contract TestUtils { 4 | 5 | function parseStorageExceptionData(bytes memory _data) public pure returns (address sender, uint wei_sent) { 6 | require(_data.length == 64); 7 | assembly { 8 | sender := mload(add(0x20, _data)) 9 | wei_sent := mload(add(0x40, _data)) 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/util/registry/RegistryUtil.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | contract RegistryUtil { 4 | 5 | function registerApp(bytes32, address, bytes4[], address[]) public pure returns (bytes memory) { 6 | return msg.data; 7 | } 8 | 9 | function registerAppVersion(bytes32, bytes32, address, bytes4[], address[]) public pure returns (bytes memory) { 10 | return msg.data; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/util/scriptExec/AppMockUtilContext.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | contract AppMockUtilContext { 4 | 5 | /// PAYABLE APP /// 6 | 7 | function pay0(bytes memory) public pure returns (bytes memory) { return msg.data; } 8 | function pay1(address, uint, bytes memory) public pure returns (bytes memory) { return msg.data; } 9 | function pay2(address, uint, address, uint, bytes memory) public pure returns (bytes memory) { return msg.data; } 10 | 11 | /// STD APP /// 12 | 13 | function std0(bytes memory) public pure returns (bytes memory) { return msg.data; } 14 | function std1(bytes32, bytes32, bytes memory) public pure returns (bytes memory) { return msg.data; } 15 | function std2(bytes32, bytes32, bytes32, bytes32, bytes memory) public pure returns (bytes memory) { return msg.data; } 16 | 17 | /// EMITS APP /// 18 | 19 | function emit0(bytes memory) public pure returns (bytes memory) { return msg.data; } 20 | function emit1top0(bytes memory) public pure returns (bytes memory) { return msg.data; } 21 | function emit1top0data(bytes memory) public pure returns (bytes memory) { return msg.data; } 22 | function emit1top4data(bytes32, bytes32, bytes32, bytes32, bytes memory) public pure returns (bytes memory) { return msg.data; } 23 | function emit2top1data(bytes32, bytes memory) public pure returns (bytes memory) { return msg.data; } 24 | function emit2top4(bytes32, bytes32, bytes32, bytes32, bytes memory) public pure returns (bytes memory) { return msg.data; } 25 | 26 | /// MIXED APP /// 27 | 28 | function req0(bytes32, bytes memory) public pure returns (bytes memory) { return msg.data; } 29 | function req1(address, uint, bytes32, bytes32, bytes memory) public pure returns (bytes memory) { return msg.data; } 30 | function req2(bytes32, bytes32, bytes32, bytes memory) public pure returns (bytes memory) { return msg.data; } 31 | function req3(address, uint, bytes32, bytes memory) public pure returns (bytes memory) { return msg.data; } 32 | function reqs0( 33 | address, address, address, address, 34 | bytes32, bytes memory 35 | ) public pure returns (bytes memory) { return msg.data; } 36 | function reqs1( 37 | address, uint, 38 | bytes32, bytes32, bytes32, bytes32, bytes memory 39 | ) public pure returns (bytes memory) { return msg.data; } 40 | function reqs2( 41 | address, uint, bytes32[4] memory, 42 | bytes32, bytes32, bytes memory 43 | ) public pure returns (bytes memory) { return msg.data; } 44 | function reqs3( 45 | address, uint, bytes32, 46 | bytes32, bytes32, bytes32, bytes32, bytes memory 47 | ) public pure returns (bytes memory) { return msg.data; } 48 | 49 | /// INVALID APP /// 50 | 51 | function inv1(bytes memory) public pure returns (bytes memory) { return msg.data; } 52 | function inv2(bytes memory) public pure returns (bytes memory) { return msg.data; } 53 | 54 | /// REVERT APP /// 55 | 56 | function rev0(bytes memory) public pure returns (bytes memory) { return msg.data; } 57 | function rev1(bytes memory) public pure returns (bytes memory) { return msg.data; } 58 | function rev2(bytes32, bytes memory) public pure returns (bytes memory) { return msg.data; } 59 | function throws1(bytes memory, bytes memory) public pure returns (bytes memory) { return msg.data; } 60 | function throws2(bytes memory, bytes memory) public pure returns (bytes memory) { return msg.data; } 61 | } 62 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // See 3 | // to customize your Truffle configuration! 4 | solc: { 5 | optimizer: { 6 | enabled: true, 7 | runs: 200 8 | } 9 | }, 10 | 11 | mocha: { 12 | reporter: 'mochawesome' 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // See 3 | // to customize your Truffle configuration! 4 | solc: { 5 | optimizer: { 6 | enabled: true, 7 | runs: 200 8 | } 9 | }, 10 | 11 | mocha: { 12 | reporter: 'mochawesome' 13 | } 14 | }; 15 | --------------------------------------------------------------------------------