├── .gitignore ├── LICENSE.txt ├── README.md ├── contracts ├── Migrations.sol ├── RLPEncode.sol └── TestingWrapper.sol ├── migrations └── 1_initial_migration.js ├── test └── rlpencode.js ├── truffle-config.js └── truffle.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Development 2 | build/ 3 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2018 Kelvin Fichter 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # solidity-rlp-encode 2 | `RLPEncode` is an [RLP encoding](https://github.com/ethereum/wiki/wiki/RLP) library written in Solidity. The original author of this library is [Bakaoh](https://github.com/bakaoh). This repository cleans up the original code and adds tests for the standard RLP encoding test cases. 3 | 4 | ## TODO 5 | - [ ] Fix support for ABIEncoderV2. 6 | - [ ] Add tests for `encodeList`. 7 | - [ ] See if there are any obvious optimizations. 8 | - [ ] Add documentation. 9 | 10 | ## Requirements 11 | This repository hosts a [Truffle](https://truffleframework.com/) project. Install truffle globally via NPM: 12 | 13 | ``` 14 | $ npm install -g truffle 15 | ``` 16 | 17 | ## Testing 18 | You can test this code by running: 19 | 20 | ``` 21 | $ truffle test 22 | ``` 23 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.0 < 0.6.0; 2 | 3 | 4 | contract Migrations { 5 | address public owner; 6 | uint public last_completed_migration; 7 | 8 | constructor() public { 9 | owner = msg.sender; 10 | } 11 | 12 | modifier restricted() { 13 | if (msg.sender == owner) _; 14 | } 15 | 16 | function setCompleted(uint completed) public restricted { 17 | last_completed_migration = completed; 18 | } 19 | 20 | function upgrade(address new_address) public restricted { 21 | Migrations upgraded = Migrations(new_address); 22 | upgraded.setCompleted(last_completed_migration); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /contracts/RLPEncode.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.0 < 0.6.0; 2 | 3 | 4 | /** 5 | * @title RLPEncode 6 | * @dev A simple RLP encoding library. 7 | * @author Bakaoh 8 | */ 9 | library RLPEncode { 10 | /* 11 | * Internal functions 12 | */ 13 | 14 | /** 15 | * @dev RLP encodes a byte string. 16 | * @param self The byte string to encode. 17 | * @return The RLP encoded string in bytes. 18 | */ 19 | function encodeBytes(bytes memory self) internal pure returns (bytes memory) { 20 | bytes memory encoded; 21 | if (self.length == 1 && uint8(self[0]) < 128) { 22 | encoded = self; 23 | } else { 24 | encoded = concat(encodeLength(self.length, 128), self); 25 | } 26 | return encoded; 27 | } 28 | 29 | /** 30 | * @dev RLP encodes a list of RLP encoded byte byte strings. 31 | * @param self The list of RLP encoded byte strings. 32 | * @return The RLP encoded list of items in bytes. 33 | */ 34 | function encodeList(bytes[] memory self) internal pure returns (bytes memory) { 35 | bytes memory list = flatten(self); 36 | return concat(encodeLength(list.length, 192), list); 37 | } 38 | 39 | /** 40 | * @dev RLP encodes a string. 41 | * @param self The string to encode. 42 | * @return The RLP encoded string in bytes. 43 | */ 44 | function encodeString(string memory self) internal pure returns (bytes memory) { 45 | return encodeBytes(bytes(self)); 46 | } 47 | 48 | /** 49 | * @dev RLP encodes an address. 50 | * @param self The address to encode. 51 | * @return The RLP encoded address in bytes. 52 | */ 53 | function encodeAddress(address self) internal pure returns (bytes memory) { 54 | bytes memory inputBytes; 55 | assembly { 56 | let m := mload(0x40) 57 | mstore(add(m, 20), xor(0x140000000000000000000000000000000000000000, self)) 58 | mstore(0x40, add(m, 52)) 59 | inputBytes := m 60 | } 61 | return encodeBytes(inputBytes); 62 | } 63 | 64 | /** 65 | * @dev RLP encodes a uint. 66 | * @param self The uint to encode. 67 | * @return The RLP encoded uint in bytes. 68 | */ 69 | function encodeUint(uint self) internal pure returns (bytes memory) { 70 | return encodeBytes(toBinary(self)); 71 | } 72 | 73 | /** 74 | * @dev RLP encodes an int. 75 | * @param self The int to encode. 76 | * @return The RLP encoded int in bytes. 77 | */ 78 | function encodeInt(int self) internal pure returns (bytes memory) { 79 | return encodeUint(uint(self)); 80 | } 81 | 82 | /** 83 | * @dev RLP encodes a bool. 84 | * @param self The bool to encode. 85 | * @return The RLP encoded bool in bytes. 86 | */ 87 | function encodeBool(bool self) internal pure returns (bytes memory) { 88 | bytes memory encoded = new bytes(1); 89 | encoded[0] = (self ? bytes1(0x01) : bytes1(0x80)); 90 | return encoded; 91 | } 92 | 93 | 94 | /* 95 | * Private functions 96 | */ 97 | 98 | /** 99 | * @dev Encode the first byte, followed by the `len` in binary form if `length` is more than 55. 100 | * @param len The length of the string or the payload. 101 | * @param offset 128 if item is string, 192 if item is list. 102 | * @return RLP encoded bytes. 103 | */ 104 | function encodeLength(uint len, uint offset) private pure returns (bytes memory) { 105 | bytes memory encoded; 106 | if (len < 56) { 107 | encoded = new bytes(1); 108 | encoded[0] = bytes32(len + offset)[31]; 109 | } else { 110 | uint lenLen; 111 | uint i = 1; 112 | while (len / i != 0) { 113 | lenLen++; 114 | i *= 256; 115 | } 116 | 117 | encoded = new bytes(lenLen + 1); 118 | encoded[0] = bytes32(lenLen + offset + 55)[31]; 119 | for(i = 1; i <= lenLen; i++) { 120 | encoded[i] = bytes32((len / (256**(lenLen-i))) % 256)[31]; 121 | } 122 | } 123 | return encoded; 124 | } 125 | 126 | /** 127 | * @dev Encode integer in big endian binary form with no leading zeroes. 128 | * @notice TODO: This should be optimized with assembly to save gas costs. 129 | * @param _x The integer to encode. 130 | * @return RLP encoded bytes. 131 | */ 132 | function toBinary(uint _x) private pure returns (bytes memory) { 133 | bytes memory b = new bytes(32); 134 | assembly { 135 | mstore(add(b, 32), _x) 136 | } 137 | uint i; 138 | for (i = 0; i < 32; i++) { 139 | if (b[i] != 0) { 140 | break; 141 | } 142 | } 143 | bytes memory res = new bytes(32 - i); 144 | for (uint j = 0; j < res.length; j++) { 145 | res[j] = b[i++]; 146 | } 147 | return res; 148 | } 149 | 150 | /** 151 | * @dev Copies a piece of memory to another location. 152 | * @notice From: https://github.com/Arachnid/solidity-stringutils/blob/master/src/strings.sol. 153 | * @param _dest Destination location. 154 | * @param _src Source location. 155 | * @param _len Length of memory to copy. 156 | */ 157 | function memcpy(uint _dest, uint _src, uint _len) private pure { 158 | uint dest = _dest; 159 | uint src = _src; 160 | uint len = _len; 161 | 162 | for(; len >= 32; len -= 32) { 163 | assembly { 164 | mstore(dest, mload(src)) 165 | } 166 | dest += 32; 167 | src += 32; 168 | } 169 | 170 | uint mask = 256 ** (32 - len) - 1; 171 | assembly { 172 | let srcpart := and(mload(src), not(mask)) 173 | let destpart := and(mload(dest), mask) 174 | mstore(dest, or(destpart, srcpart)) 175 | } 176 | } 177 | 178 | /** 179 | * @dev Flattens a list of byte strings into one byte string. 180 | * @notice From: https://github.com/sammayo/solidity-rlp-encoder/blob/master/RLPEncode.sol. 181 | * @param _list List of byte strings to flatten. 182 | * @return The flattened byte string. 183 | */ 184 | function flatten(bytes[] memory _list) private pure returns (bytes memory) { 185 | if (_list.length == 0) { 186 | return new bytes(0); 187 | } 188 | 189 | uint len; 190 | uint i; 191 | for (i = 0; i < _list.length; i++) { 192 | require(_list[i].length > 0, "An item in the list to be RLP encoded is null."); 193 | len += _list[i].length; 194 | } 195 | 196 | bytes memory flattened = new bytes(len); 197 | uint flattenedPtr; 198 | assembly { flattenedPtr := add(flattened, 0x20) } 199 | 200 | for(i = 0; i < _list.length; i++) { 201 | bytes memory item = _list[i]; 202 | 203 | uint listPtr; 204 | assembly { listPtr := add(item, 0x20)} 205 | 206 | memcpy(flattenedPtr, listPtr, item.length); 207 | flattenedPtr += _list[i].length; 208 | } 209 | 210 | return flattened; 211 | } 212 | 213 | /** 214 | * @dev Concatenates two bytes. 215 | * @notice From: https://github.com/GNSPS/solidity-bytes-utils/blob/master/contracts/BytesLib.sol. 216 | * @param _preBytes First byte string. 217 | * @param _postBytes Second byte string. 218 | * @return Both byte string combined. 219 | */ 220 | function concat(bytes memory _preBytes, bytes memory _postBytes) private pure returns (bytes memory) { 221 | bytes memory tempBytes; 222 | 223 | assembly { 224 | tempBytes := mload(0x40) 225 | 226 | let length := mload(_preBytes) 227 | mstore(tempBytes, length) 228 | 229 | let mc := add(tempBytes, 0x20) 230 | let end := add(mc, length) 231 | 232 | for { 233 | let cc := add(_preBytes, 0x20) 234 | } lt(mc, end) { 235 | mc := add(mc, 0x20) 236 | cc := add(cc, 0x20) 237 | } { 238 | mstore(mc, mload(cc)) 239 | } 240 | 241 | length := mload(_postBytes) 242 | mstore(tempBytes, add(length, mload(tempBytes))) 243 | 244 | mc := end 245 | end := add(mc, length) 246 | 247 | for { 248 | let cc := add(_postBytes, 0x20) 249 | } lt(mc, end) { 250 | mc := add(mc, 0x20) 251 | cc := add(cc, 0x20) 252 | } { 253 | mstore(mc, mload(cc)) 254 | } 255 | 256 | mstore(0x40, and( 257 | add(add(end, iszero(add(length, mload(_preBytes)))), 31), 258 | not(31) 259 | )) 260 | } 261 | 262 | return tempBytes; 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /contracts/TestingWrapper.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.0 < 0.6.0; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "./RLPEncode.sol"; 5 | 6 | 7 | /** 8 | * @title TestingWrapper 9 | * @dev Wrapper for RLPEncode for testing. 10 | */ 11 | contract TestingWrapper { 12 | /* 13 | * Public functions 14 | */ 15 | 16 | /** 17 | * @dev RLP encodes a byte string. 18 | * @param self The byte string to encode. 19 | * @return The RLP encoded string in bytes. 20 | */ 21 | function encodeBytes(bytes memory self) public pure returns (bytes memory) { 22 | return RLPEncode.encodeBytes(self); 23 | } 24 | 25 | /** 26 | * @dev RLP encodes a list of RLP encoded byte byte strings. 27 | * @param self The list of RLP encoded byte strings. 28 | * @return The RLP encoded list of items in bytes. 29 | */ 30 | function encodeList(bytes[] memory self) public pure returns (bytes memory) { 31 | return RLPEncode.encodeList(self); 32 | } 33 | 34 | /** 35 | * @dev RLP encodes a string. 36 | * @param self The string to encode. 37 | * @return The RLP encoded string in bytes. 38 | */ 39 | function encodeString(string memory self) public pure returns (bytes memory) { 40 | return RLPEncode.encodeString(self); 41 | } 42 | 43 | /** 44 | * @dev RLP encodes an address. 45 | * @param self The address to encode. 46 | * @return The RLP encoded address in bytes. 47 | */ 48 | function encodeAddress(address self) public pure returns (bytes memory) { 49 | return RLPEncode.encodeAddress(self); 50 | } 51 | 52 | /** 53 | * @dev RLP encodes a uint. 54 | * @param self The uint to encode. 55 | * @return The RLP encoded uint in bytes. 56 | */ 57 | function encodeUint(uint self) public pure returns (bytes memory) { 58 | return RLPEncode.encodeUint(self); 59 | } 60 | 61 | /** 62 | * @dev RLP encodes an int. 63 | * @param self The int to encode. 64 | * @return The RLP encoded int in bytes. 65 | */ 66 | function encodeInt(int self) public pure returns (bytes memory) { 67 | return RLPEncode.encodeInt(self); 68 | } 69 | 70 | /** 71 | * @dev RLP encodes a bool. 72 | * @param self The bool to encode. 73 | * @return The RLP encoded bool in bytes. 74 | */ 75 | function encodeBool(bool self) public pure returns (bytes memory) { 76 | return RLPEncode.encodeBool(self); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | var RLPEncode = artifacts.require("./TestingWrapper.sol"); 3 | 4 | module.exports = function(deployer) { 5 | deployer.deploy(Migrations); 6 | deployer.deploy(RLPEncode); 7 | }; 8 | -------------------------------------------------------------------------------- /test/rlpencode.js: -------------------------------------------------------------------------------- 1 | let RLPEncode = artifacts.require('TestingWrapper'); 2 | 3 | contract('RLPEncode', (accounts) => { 4 | it('should encode bytes correctly', () => { 5 | return RLPEncode.deployed().then((instance) => { 6 | return instance.encodeBytes('0xdeadbeef'); 7 | }).then((encoded) => { 8 | assert.equal(encoded, '0x84deadbeef', 'Bytes not correctly encoded'); 9 | }); 10 | }); 11 | it('should encode an encoded short integer correctly', () => { 12 | return RLPEncode.deployed().then((instance) => { 13 | return instance.encodeBytes('0x0f'); 14 | }).then((encoded) => { 15 | assert.equal(encoded, '0x0f', 'Encoded short integer not correctly encoded'); 16 | }); 17 | }); 18 | it('should encode an encoded long integer correctly', () => { 19 | return RLPEncode.deployed().then((instance) => { 20 | return instance.encodeBytes('0x0400'); 21 | }).then((encoded) => { 22 | assert.equal(encoded, '0x820400', 'Encoded long integer not correctly encoded'); 23 | }); 24 | }); 25 | 26 | it('should encode the null string correctly', () => { 27 | return RLPEncode.deployed().then((instance) => { 28 | return instance.encodeString(''); 29 | }).then((encoded) => { 30 | assert.equal(encoded, '0x80', 'Null string not correctly encoded'); 31 | }); 32 | }); 33 | it('should encode a short string correctly', () => { 34 | return RLPEncode.deployed().then((instance) => { 35 | return instance.encodeString('dog'); 36 | }).then((encoded) => { 37 | assert.equal(encoded, '0x83646f67', 'Short string not correctly encoded'); 38 | }); 39 | }); 40 | it('should encode a long string correctly', () => { 41 | return RLPEncode.deployed().then((instance) => { 42 | return instance.encodeString('Lorem ipsum dolor sit amet, consectetur adipisicing elit'); 43 | }).then((encoded) => { 44 | assert.equal(encoded, '0xb8384c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e7365637465747572206164697069736963696e6720656c6974', 'Long string not correctly encoded'); 45 | }); 46 | }); 47 | 48 | it('should encode an address correctly', () => { 49 | return RLPEncode.deployed().then((instance) => { 50 | return instance.encodeAddress('0xaa6e07ac6b69723ecadfe1483a75d72e7740ecdc'); 51 | }).then((encoded) => { 52 | assert.equal(encoded, '0x94aa6e07ac6b69723ecadfe1483a75d72e7740ecdc', 'Address not correctly encoded'); 53 | }); 54 | }); 55 | 56 | it('should encode zero correctly', () => { 57 | return RLPEncode.deployed().then((instance) => { 58 | return instance.encodeUint(0); 59 | }).then((encoded) => { 60 | assert.equal(encoded, '0x80', 'Zero not correctly encoded'); 61 | }); 62 | }); 63 | it('should encode an integer correctly', () => { 64 | return RLPEncode.deployed().then((instance) => { 65 | return instance.encodeUint(12345); 66 | }).then((encoded) => { 67 | assert.equal(encoded, '0x823039', 'Integer not correctly encoded'); 68 | }); 69 | }); 70 | 71 | it('should encode "true" correctly', () => { 72 | return RLPEncode.deployed().then((instance) => { 73 | return instance.encodeBool(true); 74 | }).then((encoded) => { 75 | assert.equal(encoded, '0x01', '"true" not correctly encoded'); 76 | }); 77 | }); 78 | it('should encode "false" correctly', () => { 79 | return RLPEncode.deployed().then((instance) => { 80 | return instance.encodeBool(false); 81 | }).then((encoded) => { 82 | assert.equal(encoded, '0x80', '"false" not correctly encoded'); 83 | }); 84 | }); 85 | it('should encode 128 correctly', () => { 86 | return RLPEncode.deployed().then((instance) => { 87 | return instance.encodeUint(128); 88 | }).then((encoded) => { 89 | assert.equal(encoded, '0x8180', ' Integer 128 not correctly encoded'); 90 | }); 91 | }); 92 | }); 93 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | networks: { 3 | development: { 4 | host: "127.0.0.1", 5 | port: 8545, 6 | network_id: "*" // Match any network id 7 | } 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | networks: { 3 | development: { 4 | host: "127.0.0.1", 5 | port: 8545, 6 | network_id: "*" // Match any network id 7 | } 8 | } 9 | }; 10 | --------------------------------------------------------------------------------