├── LICENSE ├── README.md ├── contracts ├── .gitkeep ├── Lambdeth.sol └── LambdethMethodTest.sol ├── migrations └── .gitkeep ├── package-lock.json ├── package.json ├── test └── methods.js ├── truffle-config.js ├── zos.json ├── zos.mainnet.json └── zos.ropsten.json /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Levi S Morris 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lambdeth 2 | 3 | Lambdeth is a functional library built in Solidity. 4 | 5 | Install for use through ZeppelinOS via npm: https://www.npmjs.com/package/lambdeth 6 | 7 | ## Methods 8 | 9 | ### concat(uint[], uint[]) 10 | Returns a new array containing all elements from two input arrays. 11 | 12 | ### contains(uint[], uint value) 13 | Returns true if a specified value is present in the array and false if not. 14 | 15 | ### filter(uint[], bytes4 callback) 16 | Returns a new array with values for which the predicate returns false. 17 | Callback must be passed as a bytes4 function selector. 18 | 19 | ### map(uint[], bytes4 callback) 20 | Returns a new array of equal lenth containing transformed elements. 21 | Callback must be passed as a bytes4 function selector and must return a bool. 22 | 23 | ### slice(uint[], uint start, uint end) 24 | Returns a new array containing a specified subset of the original array; inclusive of start and exclusive of end. 25 | 26 | ### unique(uint[]) 27 | Returns a new array containing only 1 copy of each value present in the original array. 28 | ____________________________________________________________________________________________________________________________________ 29 | # Usage 30 | 31 | Install via NPM for use in projects built on ZeppelinOS: `npm install lambdeth`. 32 | 33 | To use a function from the library: 34 | Lambdeth.{functionName} 35 | -------------------------------------------------------------------------------- /contracts/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siromivel/lambdeth/121788c9c173586e5adaabbfdc942221e947ea01/contracts/.gitkeep -------------------------------------------------------------------------------- /contracts/Lambdeth.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | /** 4 | * @title Lambdeth 5 | * @dev Helpful methods for working with arrays 6 | */ 7 | contract Lambdeth { 8 | 9 | /** 10 | * @dev Iterates an array and returns true if the specified value is present in the array 11 | */ 12 | function concat(uint[] memory arr1, uint[] memory arr2) public pure returns (uint[] memory) { 13 | uint length1 = arr1.length; 14 | uint length2 = arr2.length; 15 | uint finalLength = length1 + length2; 16 | 17 | uint[] memory returnArray = new uint[](finalLength); 18 | 19 | for (uint i = 0; i < length1; i++) { 20 | returnArray[i] = arr1[i]; 21 | } 22 | 23 | for (uint j = 0; j < length2; j++) { 24 | returnArray[length1 + j] = arr2[j]; 25 | } 26 | 27 | return returnArray; 28 | } 29 | 30 | /** 31 | * @dev Iterates an array and returns true if the specified value is present in the array 32 | */ 33 | function contains(uint[] memory arr, uint value) public pure returns (bool) { 34 | uint length = arr.length; 35 | 36 | for (uint i = 0; i < length; i++) { 37 | if (arr[i] == value) return true; 38 | } 39 | return false; 40 | } 41 | 42 | /** 43 | * @dev Iterates an array and returns a new array with values for which the predicate returns false 44 | */ 45 | function filter(uint[] memory arr, bytes4 cb) public view returns (uint[] memory) { 46 | uint length = arr.length; 47 | uint offset = 0; 48 | uint[] memory filterArray = new uint[](length); 49 | 50 | for (uint i = 0; i < length; i++) { 51 | (bool success, bytes memory data) = msg.sender.staticcall(abi.encodeWithSelector(cb, arr[i])); 52 | 53 | require(success, "callback failed"); 54 | bool keep = bytesToBool(data); 55 | 56 | if (keep) { 57 | filterArray[i - offset] = arr[i]; 58 | } else { 59 | offset++; 60 | } 61 | } 62 | 63 | return slice(filterArray, 0, length - offset); 64 | } 65 | 66 | /** 67 | * @dev Iterates an array and returns a new array of equal lenth containing transformed elements 68 | */ 69 | function map(uint[] memory arr, bytes4 cb) public view returns (uint[] memory) { 70 | uint length = arr.length; 71 | uint[] memory returnArray = new uint[](length); 72 | 73 | for (uint i = 0; i < length; i++) { 74 | (bool success, bytes memory data) = msg.sender.staticcall(abi.encodeWithSelector(cb, arr[i])); 75 | 76 | require(success, "callback failed"); 77 | returnArray[i] = bytesToUint(data, 0x00); 78 | } 79 | 80 | return returnArray; 81 | } 82 | 83 | /** 84 | * @dev Returns a sub-array of the input array inclusive of start end exclusive of end 85 | **/ 86 | function slice(uint[] memory arr, uint start, uint end) public pure returns (uint[] memory) { 87 | require(end > start, "start must be before end"); 88 | uint sliceLength = end - start; 89 | uint[] memory returnArray = new uint[](sliceLength); 90 | 91 | for (uint i = 0; i < sliceLength; i++) { 92 | returnArray[i] = arr[i + start]; 93 | } 94 | 95 | return returnArray; 96 | } 97 | 98 | /** 99 | * @dev Returns an array containing no more than 1 entry for any value 100 | */ 101 | function unique(uint[] memory arr) public pure returns (uint[] memory) { 102 | uint length = arr.length; 103 | uint offset = 0; 104 | uint[] memory returnArray = new uint[](length); 105 | 106 | for (uint i = 0; i < length; i++) { 107 | uint value = arr[i]; 108 | uint nextSlot = i - offset; 109 | 110 | if (i == 0 || !contains(slice(returnArray, 0, nextSlot), value)) { 111 | returnArray[nextSlot] = value; 112 | } else { 113 | offset++; 114 | } 115 | } 116 | 117 | return slice(returnArray, 0, length - offset); 118 | } 119 | 120 | // Converts bytes into bool 121 | function bytesToBool(bytes memory data) internal pure returns(bool) { 122 | uint val = bytesToUint(data, 0x00); 123 | require(val == 0 || val == 1, "Predicate returned non-boolean value"); 124 | 125 | return val == 1; 126 | } 127 | 128 | // Converts bytes into uint 129 | function bytesToUint(bytes memory data, uint start) internal pure returns (uint) { 130 | require(data.length >= start + 32, "slicing out of range"); 131 | uint val; 132 | 133 | assembly { 134 | val := mload(add(data, add(0x20, start))) 135 | } 136 | 137 | return val; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /contracts/LambdethMethodTest.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import "./Lambdeth.sol"; 4 | 5 | 6 | /** 7 | * @title LambdethMethodTest 8 | * @dev Wrapper contract for testing Lambdeth methods. Necessary for passing bytes4 function signature. 9 | */ 10 | contract LambdethMethodTest { 11 | 12 | function testConcat(Lambdeth lambdeth) public pure returns (uint[] memory) { 13 | uint[] memory testArr1 = new uint[](5); 14 | testArr1[0] = 1; 15 | testArr1[1] = 2; 16 | testArr1[2] = 3; 17 | testArr1[3] = 4; 18 | testArr1[4] = 5; 19 | 20 | uint[] memory testArr2 = new uint[](6); 21 | testArr2[0] = 6; 22 | testArr2[1] = 7; 23 | testArr2[2] = 8; 24 | testArr2[3] = 9; 25 | testArr2[4] = 0; 26 | testArr2[5] = 10; 27 | 28 | return lambdeth.concat(testArr1, testArr2); 29 | } 30 | 31 | function testContains(Lambdeth lambdeth) public pure returns (bool[] memory) { 32 | uint[] memory testArr = new uint[](10); 33 | testArr[0] = 1; 34 | testArr[1] = 0; 35 | testArr[2] = 3; 36 | testArr[3] = 300; 37 | testArr[4] = 7000; 38 | testArr[5] = 16; 39 | testArr[6] = 32; 40 | testArr[7] = 64; 41 | testArr[8] = 128; 42 | testArr[9] = 10; 43 | 44 | bool[] memory result = new bool[](2); 45 | 46 | result[0] = lambdeth.contains(testArr, 0); 47 | result[1] = lambdeth.contains(testArr, 17); 48 | 49 | return result; 50 | } 51 | 52 | function testFilter(Lambdeth lambdeth) public view returns (uint[] memory) { 53 | uint[] memory testArr = new uint[](10); 54 | testArr[0] = 1; 55 | testArr[1] = 2; 56 | testArr[2] = 3; 57 | testArr[3] = 300; 58 | testArr[4] = 7000; 59 | testArr[5] = 16; 60 | testArr[6] = 32; 61 | testArr[7] = 64; 62 | testArr[8] = 128; 63 | testArr[9] = 0; 64 | 65 | return lambdeth.filter(testArr, this.isNotAPowerOfTwo.selector); 66 | } 67 | 68 | function testMap(Lambdeth lambdeth) public view returns (uint[] memory) { 69 | uint[] memory testArr = new uint[](10); 70 | testArr[0] = 1; 71 | testArr[1] = 2; 72 | testArr[2] = 3; 73 | testArr[3] = 300; 74 | testArr[4] = 7000; 75 | testArr[5] = 16; 76 | testArr[6] = 32; 77 | testArr[7] = 64; 78 | testArr[8] = 128; 79 | testArr[9] = 0; 80 | 81 | return lambdeth.map(testArr, this.square.selector); 82 | } 83 | 84 | function testSlice(Lambdeth lambdeth) public pure returns (uint[] memory) { 85 | uint[] memory testArr = new uint[](10); 86 | testArr[0] = 1; 87 | testArr[1] = 2; 88 | testArr[2] = 3; 89 | testArr[3] = 300; 90 | testArr[4] = 7000; 91 | testArr[5] = 16; 92 | testArr[6] = 32; 93 | testArr[7] = 64; 94 | testArr[8] = 128; 95 | testArr[9] = 0; 96 | 97 | return lambdeth.slice(testArr, 3, 7); 98 | } 99 | 100 | function testUnique(Lambdeth lambdeth) public pure returns (uint[] memory) { 101 | uint[] memory testArr = new uint[](10); 102 | testArr[0] = 1; 103 | testArr[1] = 5; 104 | testArr[2] = 1; 105 | testArr[3] = 13; 106 | testArr[4] = 13; 107 | testArr[5] = 17; 108 | testArr[6] = 21; 109 | testArr[7] = 10; 110 | testArr[8] = 17; 111 | testArr[9] = 0; 112 | 113 | return lambdeth.unique(testArr); 114 | } 115 | 116 | function testUniqueWithTrailingZero(Lambdeth lambdeth) public pure returns (uint[] memory) { 117 | uint[] memory testArr = new uint[](4); 118 | 119 | testArr[0] = 2; 120 | testArr[1] = 1; 121 | testArr[2] = 2; 122 | testArr[3] = 0; 123 | 124 | return lambdeth.unique(testArr); 125 | } 126 | 127 | function square(uint n) public pure returns (uint) { 128 | return n ** 2; 129 | } 130 | 131 | function isNotAPowerOfTwo(uint n) public pure returns (bool) { 132 | return n % 2 != 0; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /migrations/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siromivel/lambdeth/121788c9c173586e5adaabbfdc942221e947ea01/migrations/.gitkeep -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lambdeth", 3 | "version": "0.2.1", 4 | "description": "Functional Programming Library for the EVM", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/siromivel/lambdeth.git" 12 | }, 13 | "author": "Tara Regan and Levi Morris", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/siromivel/lambdeth/issues" 17 | }, 18 | "homepage": "https://github.com/siromivel/lambdeth#readme", 19 | "dependencies": { 20 | "truffle": "^5.0.4", 21 | "zos": "^2.2.0", 22 | "zos-lib": "^2.2.0" 23 | }, 24 | "devDependencies": { 25 | "chai": "^4.2.0" 26 | }, 27 | "files": [ 28 | "build", 29 | "contracts", 30 | "test", 31 | "zos.json", 32 | "zos.*.json" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /test/methods.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | process.env.NODE_ENV = 'test' 3 | 4 | const Lambdeth = artifacts.require('Lambdeth') 5 | const LambdethMethodTest = artifacts.require('LambdethMethodTest') 6 | 7 | contract('Lambdeth', function () { 8 | beforeEach(async function () { 9 | this.lambdeth = await Lambdeth.new({ gas: 5000000 }) 10 | this.lambdethTest = await LambdethMethodTest.new({ gas: 5000000 }) 11 | }) 12 | 13 | describe('concat', function() { 14 | it('should concatenate two arrays', async function() { 15 | const expected = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 10] 16 | const result = await this.lambdethTest.testConcat(this.lambdeth.address) 17 | 18 | expect(transformSolidityArray(result)).to.eql(expected) 19 | }) 20 | }) 21 | 22 | describe('contains', function() { 23 | it('should return true if an array contains the passed value', async function() { 24 | const result = await this.lambdethTest.testContains(this.lambdeth.address) 25 | 26 | expect(result).to.eql([true, false]) 27 | }) 28 | }) 29 | 30 | describe('filter', function() { 31 | it('should return an array containing only elements for which the predicate function returns true', async function() { 32 | const expected = [1, 3] 33 | const result = await this.lambdethTest.testFilter(this.lambdeth.address) 34 | 35 | expect(transformSolidityArray(result)).to.eql(expected) 36 | }) 37 | }) 38 | 39 | describe('map', function() { 40 | it('should transform an array by applying a callback on every element', async function() { 41 | const expected = [1**2, 2**2, 3**2, 300**2, 7000**2, 16**2, 32**2, 64**2, 128**2, 0**2] 42 | const result = await this.lambdethTest.testMap(this.lambdeth.address) 43 | 44 | expect(transformSolidityArray(result)).to.eql(expected) 45 | }) 46 | }) 47 | 48 | describe('slice', function() { 49 | it('should return a sub-array inclusive of start and exclusive of end', async function() { 50 | const expected = [300, 7000, 16, 32] 51 | const result = await this.lambdethTest.testSlice(this.lambdeth.address) 52 | 53 | expect(transformSolidityArray(result)).to.eql(expected) 54 | }) 55 | }) 56 | 57 | describe('unique', function() { 58 | it('should return an array containing only one copy of each value', async function() { 59 | let expected = [1, 5, 13, 17, 21, 10, 0] 60 | let result = await this.lambdethTest.testUnique(this.lambdeth.address) 61 | 62 | expect(transformSolidityArray(result)).to.eql(expected) 63 | }) 64 | 65 | it('should handle arrays with a 0 in the last position', async function() { 66 | let expected = [2, 1, 0] 67 | let result = await this.lambdethTest.testUniqueWithTrailingZero(this.lambdeth.address) 68 | 69 | expect(transformSolidityArray(result)).to.eql(expected) 70 | }) 71 | }) 72 | 73 | 74 | }) 75 | 76 | function transformSolidityArray(arr) { 77 | return arr.map(val => val.words[0]) 78 | } 79 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | networks: { 3 | local: { 4 | host: 'localhost', 5 | port: 9545, 6 | gas: 5000000, 7 | gasPrice: 5e9, 8 | network_id: '*', 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /zos.json: -------------------------------------------------------------------------------- 1 | { 2 | "zosversion": "2.2", 3 | "name": "lambdeth", 4 | "version": "0.2.1", 5 | "contracts": { 6 | "Lambdeth": "Lambdeth" 7 | } 8 | } -------------------------------------------------------------------------------- /zos.mainnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "contracts": { 3 | "Lambdeth": { 4 | "address": "0xA6C8a3293f748e4ce0e83974D7738d912D0147C0", 5 | "constructorCode": "608060405234801561001057600080fd5b50611178806100206000396000f3fe", 6 | "bodyBytecodeHash": "d8ef132f69d8345da3940af5d298fbb69f5d017b02e74f79eeaf8633a7225c5d", 7 | "localBytecodeHash": "1078cd7a22afd1f021363d6903309f15a67cabe486dd8e506d796d66d11fa0d8", 8 | "deployedBytecodeHash": "1078cd7a22afd1f021363d6903309f15a67cabe486dd8e506d796d66d11fa0d8", 9 | "types": {}, 10 | "storage": [], 11 | "warnings": { 12 | "hasConstructor": false, 13 | "hasSelfDestruct": false, 14 | "hasDelegateCall": false, 15 | "hasInitialValuesInDeclarations": false, 16 | "uninitializedBaseContracts": [] 17 | } 18 | } 19 | }, 20 | "solidityLibs": {}, 21 | "proxies": { 22 | "lambdeth/Lambdeth": [ 23 | { 24 | "address": "0x37dE8af025ccFAF0bbE74Ae704fe740D52DaEC84", 25 | "version": "0.2.1", 26 | "implementation": "0xA6C8a3293f748e4ce0e83974D7738d912D0147C0" 27 | } 28 | ] 29 | }, 30 | "zosversion": "2.2", 31 | "version": "0.2.1", 32 | "proxyAdmin": { 33 | "address": "0x562f40C0d5b832541F02c369552016b904452daA" 34 | } 35 | } -------------------------------------------------------------------------------- /zos.ropsten.json: -------------------------------------------------------------------------------- 1 | { 2 | "contracts": { 3 | "Lambdeth": { 4 | "address": "0xCd1EFb36fd04BB893BE81E1cb233698d312b7DEE", 5 | "constructorCode": "608060405234801561001057600080fd5b50611178806100206000396000f3fe", 6 | "bodyBytecodeHash": "d8ef132f69d8345da3940af5d298fbb69f5d017b02e74f79eeaf8633a7225c5d", 7 | "localBytecodeHash": "1078cd7a22afd1f021363d6903309f15a67cabe486dd8e506d796d66d11fa0d8", 8 | "deployedBytecodeHash": "1078cd7a22afd1f021363d6903309f15a67cabe486dd8e506d796d66d11fa0d8", 9 | "types": {}, 10 | "storage": [], 11 | "warnings": { 12 | "hasConstructor": false, 13 | "hasSelfDestruct": false, 14 | "hasDelegateCall": false, 15 | "hasInitialValuesInDeclarations": false, 16 | "uninitializedBaseContracts": [] 17 | } 18 | } 19 | }, 20 | "solidityLibs": {}, 21 | "proxies": { 22 | "lambdeth/Lambdeth": [ 23 | { 24 | "address": "0x8C897A9a7B3C51AeD14C639F1aFCB315A18B9e75", 25 | "version": "0.2.1", 26 | "implementation": "0xCd1EFb36fd04BB893BE81E1cb233698d312b7DEE" 27 | } 28 | ] 29 | }, 30 | "zosversion": "2.2", 31 | "version": "0.2.1", 32 | "proxyAdmin": { 33 | "address": "0x5325cFAe41f23dD217D6e5Ee78D74B2C022a1b89" 34 | } 35 | } --------------------------------------------------------------------------------