diff --git a/Cargo.toml b/Cargo.toml index f6e2352..dc6a126 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,10 +27,12 @@ publish = false # Build dependencies. [dependencies] bimap = "0.6.3" +bitvec = "1.0.1" derivative = "2.2.0" downcast-rs = "1.2.0" ethnum = "1.3.2" hex = "0.4.3" +itertools = "0.10.5" serde = { version = "1.0.163", features = ["derive"] } sha3 = "0.10.8" thiserror = "1.0.40" @@ -41,7 +43,6 @@ uuid = { version = "1.3.2", features = ["v4", "fast-rng", "macro-diagnostics"] } # These are the dependencies required purely for internal development of the library such as testing or benchmarking. [dev-dependencies] anyhow = { version = "1.0.71", features = ["backtrace"] } -itertools = "0.10.5" rand = "0.8.5" serde_json = "1.0.96" diff --git a/asset/PackedEncodings.json b/asset/PackedEncodings.json new file mode 100644 index 0000000..2171de0 --- /dev/null +++ b/asset/PackedEncodings.json @@ -0,0 +1,1123 @@ +{ + "abi": [ + { + "inputs": [], + "name": "get_current", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "bool", + "name": "isEnabled", + "type": "bool" + } + ], + "internalType": "struct BytecodeExample.StructTwo", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "history", + "outputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "bool", + "name": "isEnabled", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "bool", + "name": "isEnabled", + "type": "bool" + } + ], + "name": "new_current", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "one", + "type": "uint64" + }, + { + "internalType": "uint128", + "name": "two", + "type": "uint128" + } + ], + "name": "set_data", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": { + "object": "0x608060405234801561001057600080fd5b506102db806100206000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c80631b78187d1461005157806374878662146100aa57806377ef0222146100f5578063a7a38f0b14610194575b600080fd5b6040805180820182526000808252602091820152815180830183526001546001600160a01b038116808352600160a01b90910460ff16151591830191825283519081529051151591810191909152015b60405180910390f35b6100f36100b83660046101fb565b600080546001600160801b0390921668010000000000000000026001600160c01b031990921667ffffffffffffffff90931692909217179055565b005b6100f361010336600461024d565b600280546001818101835560009290925281547f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace90910180546001600160a01b031981166001600160a01b039384169081178355845460ff600160a01b9182900416151581026001600160a81b03199384169092179190911790925583549415159091029316931692909217179055565b6101a76101a236600461028c565b6101c6565b604080516001600160a01b0390931683529015156020830152016100a1565b600281815481106101d657600080fd5b6000918252602090912001546001600160a01b0381169150600160a01b900460ff1682565b6000806040838503121561020e57600080fd5b823567ffffffffffffffff8116811461022657600080fd5b915060208301356001600160801b038116811461024257600080fd5b809150509250929050565b6000806040838503121561026057600080fd5b82356001600160a01b038116811461027757600080fd5b91506020830135801515811461024257600080fd5b60006020828403121561029e57600080fd5b503591905056fea26469706673582212207e31853dcb00b2dc55692d5f3ac5badce0163a15f68777de5100a54291a7171f64736f6c63430008130033", + "sourceMap": "65:1110:13:-:0;;;;;;;;;;;;;;;;;;;", + "linkReferences": {} + }, + "deployedBytecode": { + "object": "0x608060405234801561001057600080fd5b506004361061004c5760003560e01c80631b78187d1461005157806374878662146100aa57806377ef0222146100f5578063a7a38f0b14610194575b600080fd5b6040805180820182526000808252602091820152815180830183526001546001600160a01b038116808352600160a01b90910460ff16151591830191825283519081529051151591810191909152015b60405180910390f35b6100f36100b83660046101fb565b600080546001600160801b0390921668010000000000000000026001600160c01b031990921667ffffffffffffffff90931692909217179055565b005b6100f361010336600461024d565b600280546001818101835560009290925281547f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace90910180546001600160a01b031981166001600160a01b039384169081178355845460ff600160a01b9182900416151581026001600160a81b03199384169092179190911790925583549415159091029316931692909217179055565b6101a76101a236600461028c565b6101c6565b604080516001600160a01b0390931683529015156020830152016100a1565b600281815481106101d657600080fd5b6000918252602090912001546001600160a01b0381169150600160a01b900460ff1682565b6000806040838503121561020e57600080fd5b823567ffffffffffffffff8116811461022657600080fd5b915060208301356001600160801b038116811461024257600080fd5b809150509250929050565b6000806040838503121561026057600080fd5b82356001600160a01b038116811461027757600080fd5b91506020830135801515811461024257600080fd5b60006020828403121561029e57600080fd5b503591905056fea26469706673582212207e31853dcb00b2dc55692d5f3ac5badce0163a15f68777de5100a54291a7171f64736f6c63430008130033", + "sourceMap": "65:1110:13:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1080:93;-1:-1:-1;;;;;;;;;;;;;;;;;1152:14:13;;;;;;;1159:7;1152:14;-1:-1:-1;;;;;1152:14:13;;;;;-1:-1:-1;;;1152:14:13;;;;;;;;;;;;;1080:93;;216:58:14;;;326:24;;319:32;312:40;290:20;;;283:70;;;;189:18;1080:93:13;;;;;;;;704:105;;;;;;:::i;:::-;764:4;:14;;-1:-1:-1;;;;;788:14:13;;;;;-1:-1:-1;;;;;;788:14:13;;;764;;;;788;;;;;;;704:105;;;878:164;;;;;;:::i;:::-;946:7;:21;;959:7;946:21;;;;;-1:-1:-1;946:21:13;;;;;;;;;;;;-1:-1:-1;;;;;;946:21:13;;-1:-1:-1;;;;;946:21:13;;;;;;;;;;;-1:-1:-1;;;946:21:13;;;;;;;;;-1:-1:-1;;;;;;946:21:13;;;;;;;;;;;;;977:19;;1006:29;;;;;;;;977:19;;1006:29;;;;;;;878:164;671:26;;;;;;:::i;:::-;;:::i;:::-;;;;-1:-1:-1;;;;;1681:32:14;;;1663:51;;1757:14;;1750:22;1745:2;1730:18;;1723:50;1636:18;671:26:13;1495:284:14;671:26:13;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;671:26:13;;;-1:-1:-1;;;;671:26:13;;;;;:::o;364:481:14:-;431:6;439;492:2;480:9;471:7;467:23;463:32;460:52;;;508:1;505;498:12;460:52;547:9;534:23;597:18;590:5;586:30;579:5;576:41;566:69;;631:1;628;621:12;566:69;654:5;-1:-1:-1;711:2:14;696:18;;683:32;-1:-1:-1;;;;;746:48:14;;734:61;;724:89;;809:1;806;799:12;724:89;832:7;822:17;;;364:481;;;;;:::o;850:455::-;915:6;923;976:2;964:9;955:7;951:23;947:32;944:52;;;992:1;989;982:12;944:52;1018:23;;-1:-1:-1;;;;;1070:31:14;;1060:42;;1050:70;;1116:1;1113;1106:12;1050:70;1139:5;-1:-1:-1;1196:2:14;1181:18;;1168:32;1238:15;;1231:23;1219:36;;1209:64;;1269:1;1266;1259:12;1310:180;1369:6;1422:2;1410:9;1401:7;1397:23;1393:32;1390:52;;;1438:1;1435;1428:12;1390:52;-1:-1:-1;1461:23:14;;1310:180;-1:-1:-1;1310:180:14:o", + "linkReferences": {} + }, + "methodIdentifiers": { + "get_current()": "1b78187d", + "history(uint256)": "a7a38f0b", + "new_current(address,bool)": "77ef0222", + "set_data(uint64,uint128)": "74878662" + }, + "rawMetadata": "{\"compiler\":{\"version\":\"0.8.19+commit.7dd6d404\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"name\":\"get_current\",\"outputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"isEnabled\",\"type\":\"bool\"}],\"internalType\":\"struct BytecodeExample.StructTwo\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"history\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"isEnabled\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"isEnabled\",\"type\":\"bool\"}],\"name\":\"new_current\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"one\",\"type\":\"uint64\"},{\"internalType\":\"uint128\",\"name\":\"two\",\"type\":\"uint128\"}],\"name\":\"set_data\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/BytecodeExample.sol\":\"BytecodeExample\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":ds-test/=lib/forge-std/lib/ds-test/src/\",\":forge-std/=lib/forge-std/src/\"]},\"sources\":{\"src/BytecodeExample.sol\":{\"keccak256\":\"0x907067fed65939af2ce0fe0ecaf326fb48be4f1a9bb6274da45e46fb8c3f036b\",\"license\":\"UNLICENSED\",\"urls\":[\"bzz-raw://45a6bea4de63834587fb2ac66a11e7c196aa467ce063fc60a69eb51811a50468\",\"dweb:/ipfs/QmSFWfafcnVWXz6zY3jer5U7uUeXXNbvXi3qQqPBhWdRo5\"]}},\"version\":1}", + "metadata": { + "compiler": { + "version": "0.8.19+commit.7dd6d404" + }, + "language": "Solidity", + "output": { + "abi": [ + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "get_current", + "outputs": [ + { + "internalType": "struct BytecodeExample.StructTwo", + "name": "", + "type": "tuple", + "components": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "bool", + "name": "isEnabled", + "type": "bool" + } + ] + } + ] + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function", + "name": "history", + "outputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "bool", + "name": "isEnabled", + "type": "bool" + } + ] + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "bool", + "name": "isEnabled", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "new_current" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "one", + "type": "uint64" + }, + { + "internalType": "uint128", + "name": "two", + "type": "uint128" + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "set_data" + } + ], + "devdoc": { + "kind": "dev", + "methods": {}, + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": {}, + "version": 1 + } + }, + "settings": { + "remappings": [ + ":ds-test/=lib/forge-std/lib/ds-test/src/", + ":forge-std/=lib/forge-std/src/" + ], + "optimizer": { + "enabled": true, + "runs": 200 + }, + "metadata": { + "bytecodeHash": "ipfs" + }, + "compilationTarget": { + "src/BytecodeExample.sol": "BytecodeExample" + }, + "libraries": {} + }, + "sources": { + "src/BytecodeExample.sol": { + "keccak256": "0x907067fed65939af2ce0fe0ecaf326fb48be4f1a9bb6274da45e46fb8c3f036b", + "urls": [ + "bzz-raw://45a6bea4de63834587fb2ac66a11e7c196aa467ce063fc60a69eb51811a50468", + "dweb:/ipfs/QmSFWfafcnVWXz6zY3jer5U7uUeXXNbvXi3qQqPBhWdRo5" + ], + "license": "UNLICENSED" + } + }, + "version": 1 + }, + "ast": { + "absolutePath": "src/BytecodeExample.sol", + "id": 23991, + "exportedSymbols": { + "BytecodeExample": [ + 23990 + ] + }, + "nodeType": "SourceUnit", + "src": "39:1137:13", + "nodes": [ + { + "id": 23914, + "nodeType": "PragmaDirective", + "src": "39:24:13", + "nodes": [], + "literals": [ + "solidity", + "^", + "0.8", + ".13" + ] + }, + { + "id": 23990, + "nodeType": "ContractDefinition", + "src": "65:1110:13", + "nodes": [ + { + "id": 23919, + "nodeType": "StructDefinition", + "src": "228:65:13", + "nodes": [], + "canonicalName": "BytecodeExample.StructOne", + "members": [ + { + "constant": false, + "id": 23916, + "mutability": "mutable", + "name": "one", + "nameLocation": "262:3:13", + "nodeType": "VariableDeclaration", + "scope": 23919, + "src": "255:10:13", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + }, + "typeName": { + "id": 23915, + "name": "uint64", + "nodeType": "ElementaryTypeName", + "src": "255:6:13", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 23918, + "mutability": "mutable", + "name": "two", + "nameLocation": "283:3:13", + "nodeType": "VariableDeclaration", + "scope": 23919, + "src": "275:11:13", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint128", + "typeString": "uint128" + }, + "typeName": { + "id": 23917, + "name": "uint128", + "nodeType": "ElementaryTypeName", + "src": "275:7:13", + "typeDescriptions": { + "typeIdentifier": "t_uint128", + "typeString": "uint128" + } + }, + "visibility": "internal" + } + ], + "name": "StructOne", + "nameLocation": "235:9:13", + "scope": 23990, + "visibility": "public" + }, + { + "id": 23924, + "nodeType": "StructDefinition", + "src": "349:70:13", + "nodes": [], + "canonicalName": "BytecodeExample.StructTwo", + "members": [ + { + "constant": false, + "id": 23921, + "mutability": "mutable", + "name": "addr", + "nameLocation": "384:4:13", + "nodeType": "VariableDeclaration", + "scope": 23924, + "src": "376:12:13", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 23920, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "376:7:13", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 23923, + "mutability": "mutable", + "name": "isEnabled", + "nameLocation": "403:9:13", + "nodeType": "VariableDeclaration", + "scope": 23924, + "src": "398:14:13", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 23922, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "398:4:13", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "visibility": "internal" + } + ], + "name": "StructTwo", + "nameLocation": "356:9:13", + "scope": 23990, + "visibility": "public" + }, + { + "id": 23927, + "nodeType": "VariableDeclaration", + "src": "468:23:13", + "nodes": [], + "constant": false, + "mutability": "mutable", + "name": "data", + "nameLocation": "487:4:13", + "scope": 23990, + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_struct$_StructOne_$23919_storage", + "typeString": "struct BytecodeExample.StructOne" + }, + "typeName": { + "id": 23926, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 23925, + "name": "StructOne", + "nameLocations": [ + "468:9:13" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 23919, + "src": "468:9:13" + }, + "referencedDeclaration": 23919, + "src": "468:9:13", + "typeDescriptions": { + "typeIdentifier": "t_struct$_StructOne_$23919_storage_ptr", + "typeString": "struct BytecodeExample.StructOne" + } + }, + "visibility": "internal" + }, + { + "id": 23930, + "nodeType": "VariableDeclaration", + "src": "639:26:13", + "nodes": [], + "constant": false, + "mutability": "mutable", + "name": "current", + "nameLocation": "658:7:13", + "scope": 23990, + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_struct$_StructTwo_$23924_storage", + "typeString": "struct BytecodeExample.StructTwo" + }, + "typeName": { + "id": 23929, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 23928, + "name": "StructTwo", + "nameLocations": [ + "639:9:13" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 23924, + "src": "639:9:13" + }, + "referencedDeclaration": 23924, + "src": "639:9:13", + "typeDescriptions": { + "typeIdentifier": "t_struct$_StructTwo_$23924_storage_ptr", + "typeString": "struct BytecodeExample.StructTwo" + } + }, + "visibility": "internal" + }, + { + "id": 23934, + "nodeType": "VariableDeclaration", + "src": "671:26:13", + "nodes": [], + "constant": false, + "functionSelector": "a7a38f0b", + "mutability": "mutable", + "name": "history", + "nameLocation": "690:7:13", + "scope": 23990, + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_struct$_StructTwo_$23924_storage_$dyn_storage", + "typeString": "struct BytecodeExample.StructTwo[]" + }, + "typeName": { + "baseType": { + "id": 23932, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 23931, + "name": "StructTwo", + "nameLocations": [ + "671:9:13" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 23924, + "src": "671:9:13" + }, + "referencedDeclaration": 23924, + "src": "671:9:13", + "typeDescriptions": { + "typeIdentifier": "t_struct$_StructTwo_$23924_storage_ptr", + "typeString": "struct BytecodeExample.StructTwo" + } + }, + "id": 23933, + "nodeType": "ArrayTypeName", + "src": "671:11:13", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_struct$_StructTwo_$23924_storage_$dyn_storage_ptr", + "typeString": "struct BytecodeExample.StructTwo[]" + } + }, + "visibility": "public" + }, + { + "id": 23954, + "nodeType": "FunctionDefinition", + "src": "704:105:13", + "nodes": [], + "body": { + "id": 23953, + "nodeType": "Block", + "src": "754:55:13", + "nodes": [], + "statements": [ + { + "expression": { + "id": 23945, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 23941, + "name": "data", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 23927, + "src": "764:4:13", + "typeDescriptions": { + "typeIdentifier": "t_struct$_StructOne_$23919_storage", + "typeString": "struct BytecodeExample.StructOne storage ref" + } + }, + "id": 23943, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "769:3:13", + "memberName": "one", + "nodeType": "MemberAccess", + "referencedDeclaration": 23916, + "src": "764:8:13", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "id": 23944, + "name": "one", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 23936, + "src": "775:3:13", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + } + }, + "src": "764:14:13", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + } + }, + "id": 23946, + "nodeType": "ExpressionStatement", + "src": "764:14:13" + }, + { + "expression": { + "id": 23951, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 23947, + "name": "data", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 23927, + "src": "788:4:13", + "typeDescriptions": { + "typeIdentifier": "t_struct$_StructOne_$23919_storage", + "typeString": "struct BytecodeExample.StructOne storage ref" + } + }, + "id": 23949, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "793:3:13", + "memberName": "two", + "nodeType": "MemberAccess", + "referencedDeclaration": 23918, + "src": "788:8:13", + "typeDescriptions": { + "typeIdentifier": "t_uint128", + "typeString": "uint128" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "id": 23950, + "name": "two", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 23938, + "src": "799:3:13", + "typeDescriptions": { + "typeIdentifier": "t_uint128", + "typeString": "uint128" + } + }, + "src": "788:14:13", + "typeDescriptions": { + "typeIdentifier": "t_uint128", + "typeString": "uint128" + } + }, + "id": 23952, + "nodeType": "ExpressionStatement", + "src": "788:14:13" + } + ] + }, + "functionSelector": "74878662", + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "set_data", + "nameLocation": "713:8:13", + "parameters": { + "id": 23939, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 23936, + "mutability": "mutable", + "name": "one", + "nameLocation": "729:3:13", + "nodeType": "VariableDeclaration", + "scope": 23954, + "src": "722:10:13", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + }, + "typeName": { + "id": 23935, + "name": "uint64", + "nodeType": "ElementaryTypeName", + "src": "722:6:13", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 23938, + "mutability": "mutable", + "name": "two", + "nameLocation": "742:3:13", + "nodeType": "VariableDeclaration", + "scope": 23954, + "src": "734:11:13", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint128", + "typeString": "uint128" + }, + "typeName": { + "id": 23937, + "name": "uint128", + "nodeType": "ElementaryTypeName", + "src": "734:7:13", + "typeDescriptions": { + "typeIdentifier": "t_uint128", + "typeString": "uint128" + } + }, + "visibility": "internal" + } + ], + "src": "721:25:13" + }, + "returnParameters": { + "id": 23940, + "nodeType": "ParameterList", + "parameters": [], + "src": "754:0:13" + }, + "scope": 23990, + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "id": 23980, + "nodeType": "FunctionDefinition", + "src": "878:164:13", + "nodes": [], + "body": { + "id": 23979, + "nodeType": "Block", + "src": "936:106:13", + "nodes": [], + "statements": [ + { + "expression": { + "arguments": [ + { + "id": 23964, + "name": "current", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 23930, + "src": "959:7:13", + "typeDescriptions": { + "typeIdentifier": "t_struct$_StructTwo_$23924_storage", + "typeString": "struct BytecodeExample.StructTwo storage ref" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_struct$_StructTwo_$23924_storage", + "typeString": "struct BytecodeExample.StructTwo storage ref" + } + ], + "expression": { + "id": 23961, + "name": "history", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 23934, + "src": "946:7:13", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_struct$_StructTwo_$23924_storage_$dyn_storage", + "typeString": "struct BytecodeExample.StructTwo storage ref[] storage ref" + } + }, + "id": 23963, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberLocation": "954:4:13", + "memberName": "push", + "nodeType": "MemberAccess", + "src": "946:12:13", + "typeDescriptions": { + "typeIdentifier": "t_function_arraypush_nonpayable$_t_array$_t_struct$_StructTwo_$23924_storage_$dyn_storage_ptr_$_t_struct$_StructTwo_$23924_storage_$returns$__$attached_to$_t_array$_t_struct$_StructTwo_$23924_storage_$dyn_storage_ptr_$", + "typeString": "function (struct BytecodeExample.StructTwo storage ref[] storage pointer,struct BytecodeExample.StructTwo storage ref)" + } + }, + "id": 23965, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "946:21:13", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 23966, + "nodeType": "ExpressionStatement", + "src": "946:21:13" + }, + { + "expression": { + "id": 23971, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 23967, + "name": "current", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 23930, + "src": "977:7:13", + "typeDescriptions": { + "typeIdentifier": "t_struct$_StructTwo_$23924_storage", + "typeString": "struct BytecodeExample.StructTwo storage ref" + } + }, + "id": 23969, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "985:4:13", + "memberName": "addr", + "nodeType": "MemberAccess", + "referencedDeclaration": 23921, + "src": "977:12:13", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "id": 23970, + "name": "addr", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 23956, + "src": "992:4:13", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "977:19:13", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 23972, + "nodeType": "ExpressionStatement", + "src": "977:19:13" + }, + { + "expression": { + "id": 23977, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 23973, + "name": "current", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 23930, + "src": "1006:7:13", + "typeDescriptions": { + "typeIdentifier": "t_struct$_StructTwo_$23924_storage", + "typeString": "struct BytecodeExample.StructTwo storage ref" + } + }, + "id": 23975, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1014:9:13", + "memberName": "isEnabled", + "nodeType": "MemberAccess", + "referencedDeclaration": 23923, + "src": "1006:17:13", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "id": 23976, + "name": "isEnabled", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 23958, + "src": "1026:9:13", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "src": "1006:29:13", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "id": 23978, + "nodeType": "ExpressionStatement", + "src": "1006:29:13" + } + ] + }, + "functionSelector": "77ef0222", + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "new_current", + "nameLocation": "887:11:13", + "parameters": { + "id": 23959, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 23956, + "mutability": "mutable", + "name": "addr", + "nameLocation": "907:4:13", + "nodeType": "VariableDeclaration", + "scope": 23980, + "src": "899:12:13", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 23955, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "899:7:13", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 23958, + "mutability": "mutable", + "name": "isEnabled", + "nameLocation": "918:9:13", + "nodeType": "VariableDeclaration", + "scope": 23980, + "src": "913:14:13", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 23957, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "913:4:13", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "visibility": "internal" + } + ], + "src": "898:30:13" + }, + "returnParameters": { + "id": 23960, + "nodeType": "ParameterList", + "parameters": [], + "src": "936:0:13" + }, + "scope": 23990, + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "id": 23989, + "nodeType": "FunctionDefinition", + "src": "1080:93:13", + "nodes": [], + "body": { + "id": 23988, + "nodeType": "Block", + "src": "1142:31:13", + "nodes": [], + "statements": [ + { + "expression": { + "id": 23986, + "name": "current", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 23930, + "src": "1159:7:13", + "typeDescriptions": { + "typeIdentifier": "t_struct$_StructTwo_$23924_storage", + "typeString": "struct BytecodeExample.StructTwo storage ref" + } + }, + "functionReturnParameters": 23985, + "id": 23987, + "nodeType": "Return", + "src": "1152:14:13" + } + ] + }, + "functionSelector": "1b78187d", + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "get_current", + "nameLocation": "1089:11:13", + "parameters": { + "id": 23981, + "nodeType": "ParameterList", + "parameters": [], + "src": "1100:2:13" + }, + "returnParameters": { + "id": 23985, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 23984, + "mutability": "mutable", + "name": "", + "nameLocation": "-1:-1:-1", + "nodeType": "VariableDeclaration", + "scope": 23989, + "src": "1124:16:13", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_struct$_StructTwo_$23924_memory_ptr", + "typeString": "struct BytecodeExample.StructTwo" + }, + "typeName": { + "id": 23983, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 23982, + "name": "StructTwo", + "nameLocations": [ + "1124:9:13" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 23924, + "src": "1124:9:13" + }, + "referencedDeclaration": 23924, + "src": "1124:9:13", + "typeDescriptions": { + "typeIdentifier": "t_struct$_StructTwo_$23924_storage_ptr", + "typeString": "struct BytecodeExample.StructTwo" + } + }, + "visibility": "internal" + } + ], + "src": "1123:18:13" + }, + "scope": 23990, + "stateMutability": "view", + "virtual": false, + "visibility": "public" + } + ], + "abstract": false, + "baseContracts": [], + "canonicalName": "BytecodeExample", + "contractDependencies": [], + "contractKind": "contract", + "fullyImplemented": true, + "linearizedBaseContracts": [ + 23990 + ], + "name": "BytecodeExample", + "nameLocation": "74:15:13", + "scope": 23991, + "usedErrors": [] + } + ], + "license": "UNLICENSED" + }, + "id": 13 +} \ No newline at end of file diff --git a/asset/PackedEncodings.sol b/asset/PackedEncodings.sol new file mode 100644 index 0000000..bfda3a5 --- /dev/null +++ b/asset/PackedEncodings.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract BytecodeExample { + + // This one is only used in a storage slot, so should be indistinguishable + // from a packed encoding (done so we force it) + struct StructOne { + uint64 one; + uint128 two; + } + + // This one we use in a dynamic array as well + struct StructTwo { + address addr; + bool isEnabled; + } + + // This struct is just used directly. + StructOne internal data; + + // Here we create a connection between a dynamic array and a storage slot. + // We should be able to infer a struct type for the slot. + StructTwo internal current; + StructTwo[] public history; + + function set_data(uint64 one, uint128 two) public { + data.one = one; + data.two = two; + } + + // Updates the current and adds the old one to the history + function new_current(address addr, bool isEnabled) public { + history.push(current); + current.addr = addr; + current.isEnabled = isEnabled; + } + + // A getter for the current + function get_current() public view returns (StructTwo memory) { + return current; + } +} diff --git a/asset/ReplaceMeForTesting.json b/asset/ReplaceMeForTesting.json index adddc9d..ebbf9c3 100644 --- a/asset/ReplaceMeForTesting.json +++ b/asset/ReplaceMeForTesting.json @@ -3,9 +3,9 @@ { "inputs": [ { - "internalType": "uint256", + "internalType": "uint128", "name": "key", - "type": "uint256" + "type": "uint128" }, { "internalType": "uint256", @@ -21,32 +21,52 @@ { "inputs": [ { - "internalType": "uint256", + "internalType": "uint64", "name": "number", - "type": "uint256" + "type": "uint64" } ], "name": "append", "outputs": [], "stateMutability": "nonpayable", "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "read", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" } ], "bytecode": { - "object": "0x608060405234801561001057600080fd5b5060d48061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c8063771602f7146037578063e33b870714605c575b600080fd5b605a6042366004609b565b60009182526020828152604080842090915290912055565b005b605a606736600460bc565b6001805480820182556000919091527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf60155565b6000806040838503121560ad57600080fd5b50508035926020909101359150565b60006020828403121560cd57600080fd5b503591905056", - "sourceMap": "65:306:17:-:0;;;;;;;;;;;;;;;;;;;", + "object": "0x608060405234801561001057600080fd5b5061022d806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80633b076df414610046578063871d7573146100b9578063ed2e5a97146100e9575b600080fd5b6100b761005436600461015f565b6001805480820182556000919091527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf66004820401805460039092166008026101000a67ffffffffffffffff818102199093169390921691909102919091179055565b005b6100b76100c7366004610190565b6001600160801b03909116600090815260208181526040808320909152902055565b6100fc6100f73660046101c8565b610119565b60405167ffffffffffffffff909116815260200160405180910390f35b60006001828154811061012e5761012e6101e1565b90600052602060002090600491828204019190066008029054906101000a900467ffffffffffffffff169050919050565b60006020828403121561017157600080fd5b813567ffffffffffffffff8116811461018957600080fd5b9392505050565b600080604083850312156101a357600080fd5b82356001600160801b03811681146101ba57600080fd5b946020939093013593505050565b6000602082840312156101da57600080fd5b5035919050565b634e487b7160e01b600052603260045260246000fdfea26469706673582212206e123127fa4e1b84a05360ff2312642e7eca667c5e4bb91600d5fadf7744889064736f6c63430008130033", + "sourceMap": "65:410:19:-:0;;;;;;;;;;;;;;;;;;;", "linkReferences": {} }, "deployedBytecode": { - "object": "0x6080604052348015600f57600080fd5b506004361060325760003560e01c8063771602f7146037578063e33b870714605c575b600080fd5b605a6042366004609b565b60009182526020828152604080842090915290912055565b005b605a606736600460bc565b6001805480820182556000919091527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf60155565b6000806040838503121560ad57600080fd5b50508035926020909101359150565b60006020828403121560cd57600080fd5b503591905056", - "sourceMap": "65:306:17:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;198:89;;;;;;:::i;:::-;256:6;:11;;;;;;;;;;;:16;;;;;;:24;198:89;;;293:76;;;;;;:::i;:::-;342:7;:20;;;;;;;-1:-1:-1;342:20:17;;;;;;;293:76;14:248:19;82:6;90;143:2;131:9;122:7;118:23;114:32;111:52;;;159:1;156;149:12;111:52;-1:-1:-1;;182:23:19;;;252:2;237:18;;;224:32;;-1:-1:-1;14:248:19:o;267:180::-;326:6;379:2;367:9;358:7;354:23;350:32;347:52;;;395:1;392;385:12;347:52;-1:-1:-1;418:23:19;;267:180;-1:-1:-1;267:180:19:o", + "object": "0x608060405234801561001057600080fd5b50600436106100415760003560e01c80633b076df414610046578063871d7573146100b9578063ed2e5a97146100e9575b600080fd5b6100b761005436600461015f565b6001805480820182556000919091527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf66004820401805460039092166008026101000a67ffffffffffffffff818102199093169390921691909102919091179055565b005b6100b76100c7366004610190565b6001600160801b03909116600090815260208181526040808320909152902055565b6100fc6100f73660046101c8565b610119565b60405167ffffffffffffffff909116815260200160405180910390f35b60006001828154811061012e5761012e6101e1565b90600052602060002090600491828204019190066008029054906101000a900467ffffffffffffffff169050919050565b60006020828403121561017157600080fd5b813567ffffffffffffffff8116811461018957600080fd5b9392505050565b600080604083850312156101a357600080fd5b82356001600160801b03811681146101ba57600080fd5b946020939093013593505050565b6000602082840312156101da57600080fd5b5035919050565b634e487b7160e01b600052603260045260246000fdfea26469706673582212206e123127fa4e1b84a05360ff2312642e7eca667c5e4bb91600d5fadf7744889064736f6c63430008130033", + "sourceMap": "65:410:19:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;296:75;;;;;;:::i;:::-;344:7;:20;;;;;;;-1:-1:-1;344:20:19;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;296:75;;;199:91;;;;;;:::i;:::-;-1:-1:-1;;;;;257:13:19;;;:8;:13;;;;;;;;;;;:18;;;;;:26;199:91;377:96;;;;;;:::i;:::-;;:::i;:::-;;;1036:18:21;1024:31;;;1006:50;;994:2;979:18;377:96:19;;;;;;;;427:6;452:7;460:5;452:14;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;445:21;;377:96;;;:::o;14:284:21:-;72:6;125:2;113:9;104:7;100:23;96:32;93:52;;;141:1;138;131:12;93:52;180:9;167:23;230:18;223:5;219:30;212:5;209:41;199:69;;264:1;261;254:12;199:69;287:5;14:284;-1:-1:-1;;;14:284:21:o;303:369::-;371:6;379;432:2;420:9;411:7;407:23;403:32;400:52;;;448:1;445;438:12;400:52;487:9;474:23;-1:-1:-1;;;;;530:5:21;526:46;519:5;516:57;506:85;;587:1;584;577:12;506:85;610:5;662:2;647:18;;;;634:32;;-1:-1:-1;;;303:369:21:o;677:180::-;736:6;789:2;777:9;768:7;764:23;760:32;757:52;;;805:1;802;795:12;757:52;-1:-1:-1;828:23:21;;677:180;-1:-1:-1;677:180:21:o;1067:127::-;1128:10;1123:3;1119:20;1116:1;1109:31;1159:4;1156:1;1149:15;1183:4;1180:1;1173:15", "linkReferences": {} }, "methodIdentifiers": { - "add(uint256,uint256)": "771602f7", - "append(uint256)": "e33b8707" + "add(uint128,uint256)": "871d7573", + "append(uint64)": "3b076df4", + "read(uint256)": "ed2e5a97" }, - "rawMetadata": "{\"compiler\":{\"version\":\"0.8.19+commit.7dd6d404\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"key\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"add\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"number\",\"type\":\"uint256\"}],\"name\":\"append\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/BytecodeExample.sol\":\"BytecodeExample\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"appendCBOR\":false,\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":ds-test/=lib/forge-std/lib/ds-test/src/\",\":forge-std/=lib/forge-std/src/\"]},\"sources\":{\"src/BytecodeExample.sol\":{\"keccak256\":\"0x6e171e6e9b0498b21224a8cb15e7bb1c459a828986303e029052de8f586597db\",\"license\":\"UNLICENSED\",\"urls\":[\"bzz-raw://58fd0ec6be47112ba3671fdfd7a6a39774894fe0e314740fd5aecc87cca92115\",\"dweb:/ipfs/QmTgRhkdWUjvBEUoho4eGuGgagwcg5FHWKfAEcoJBKny3h\"]}},\"version\":1}", + "rawMetadata": "{\"compiler\":{\"version\":\"0.8.19+commit.7dd6d404\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"uint128\",\"name\":\"key\",\"type\":\"uint128\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"add\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"number\",\"type\":\"uint64\"}],\"name\":\"append\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"}],\"name\":\"read\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/BytecodeExample.sol\":\"BytecodeExample\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":ds-test/=lib/forge-std/lib/ds-test/src/\",\":forge-std/=lib/forge-std/src/\"]},\"sources\":{\"src/BytecodeExample.sol\":{\"keccak256\":\"0x2adb29206e84ee9eea1c3477f5e4b88804ea0e08287ce7c34d9e71a45b73c04f\",\"license\":\"UNLICENSED\",\"urls\":[\"bzz-raw://f01c75aaeedab5e22c8cd6439da0e844e0c7deeea6af22464f25f801e15df55e\",\"dweb:/ipfs/QmVR6apvqZxt6qpcCGHxe6HKBS9XDuMCJdZNvrduPpVy7J\"]}},\"version\":1}", "metadata": { "compiler": { "version": "0.8.19+commit.7dd6d404" @@ -57,9 +77,9 @@ { "inputs": [ { - "internalType": "uint256", + "internalType": "uint128", "name": "key", - "type": "uint256" + "type": "uint128" }, { "internalType": "uint256", @@ -74,14 +94,33 @@ { "inputs": [ { - "internalType": "uint256", + "internalType": "uint64", "name": "number", - "type": "uint256" + "type": "uint64" } ], "stateMutability": "nonpayable", "type": "function", "name": "append" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function", + "name": "read", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ] } ], "devdoc": { @@ -105,8 +144,7 @@ "runs": 200 }, "metadata": { - "bytecodeHash": "none", - "appendCBOR": false + "bytecodeHash": "ipfs" }, "compilationTarget": { "src/BytecodeExample.sol": "BytecodeExample" @@ -115,10 +153,10 @@ }, "sources": { "src/BytecodeExample.sol": { - "keccak256": "0x6e171e6e9b0498b21224a8cb15e7bb1c459a828986303e029052de8f586597db", + "keccak256": "0x2adb29206e84ee9eea1c3477f5e4b88804ea0e08287ce7c34d9e71a45b73c04f", "urls": [ - "bzz-raw://58fd0ec6be47112ba3671fdfd7a6a39774894fe0e314740fd5aecc87cca92115", - "dweb:/ipfs/QmTgRhkdWUjvBEUoho4eGuGgagwcg5FHWKfAEcoJBKny3h" + "bzz-raw://f01c75aaeedab5e22c8cd6439da0e844e0c7deeea6af22464f25f801e15df55e", + "dweb:/ipfs/QmVR6apvqZxt6qpcCGHxe6HKBS9XDuMCJdZNvrduPpVy7J" ], "license": "UNLICENSED" } @@ -127,19 +165,19 @@ }, "ast": { "absolutePath": "src/BytecodeExample.sol", - "id": 29157, + "id": 29225, "exportedSymbols": { "BytecodeExample": [ - 29156 + 29224 ] }, "nodeType": "SourceUnit", - "src": "39:333:17", + "src": "39:437:19", "nodes": [ { - "id": 29118, + "id": 29174, "nodeType": "PragmaDirective", - "src": "39:24:17", + "src": "39:24:19", "nodes": [], "literals": [ "solidity", @@ -149,75 +187,75 @@ ] }, { - "id": 29156, + "id": 29224, "nodeType": "ContractDefinition", - "src": "65:306:17", + "src": "65:410:19", "nodes": [ { - "id": 29124, + "id": 29180, "nodeType": "VariableDeclaration", - "src": "96:63:17", + "src": "96:65:19", "nodes": [], "constant": false, "mutability": "mutable", - "name": "number", - "nameLocation": "153:6:17", - "scope": 29156, + "name": "mappings", + "nameLocation": "153:8:19", + "scope": 29224, "stateVariable": true, "storageLocation": "default", "typeDescriptions": { - "typeIdentifier": "t_mapping$_t_uint256_$_t_mapping$_t_uint256_$_t_uint256_$_$", - "typeString": "mapping(uint256 => mapping(uint256 => uint256))" + "typeIdentifier": "t_mapping$_t_uint128_$_t_mapping$_t_uint128_$_t_uint256_$_$", + "typeString": "mapping(uint128 => mapping(uint128 => uint256))" }, "typeName": { - "id": 29123, + "id": 29179, "keyName": "", "keyNameLocation": "-1:-1:-1", "keyType": { - "id": 29119, - "name": "uint256", + "id": 29175, + "name": "uint128", "nodeType": "ElementaryTypeName", - "src": "104:7:17", + "src": "104:7:19", "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" + "typeIdentifier": "t_uint128", + "typeString": "uint128" } }, "nodeType": "Mapping", - "src": "96:47:17", + "src": "96:47:19", "typeDescriptions": { - "typeIdentifier": "t_mapping$_t_uint256_$_t_mapping$_t_uint256_$_t_uint256_$_$", - "typeString": "mapping(uint256 => mapping(uint256 => uint256))" + "typeIdentifier": "t_mapping$_t_uint128_$_t_mapping$_t_uint128_$_t_uint256_$_$", + "typeString": "mapping(uint128 => mapping(uint128 => uint256))" }, "valueName": "", "valueNameLocation": "-1:-1:-1", "valueType": { - "id": 29122, + "id": 29178, "keyName": "", "keyNameLocation": "-1:-1:-1", "keyType": { - "id": 29120, - "name": "uint256", + "id": 29176, + "name": "uint128", "nodeType": "ElementaryTypeName", - "src": "123:7:17", + "src": "123:7:19", "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" + "typeIdentifier": "t_uint128", + "typeString": "uint128" } }, "nodeType": "Mapping", - "src": "115:27:17", + "src": "115:27:19", "typeDescriptions": { - "typeIdentifier": "t_mapping$_t_uint256_$_t_uint256_$", - "typeString": "mapping(uint256 => uint256)" + "typeIdentifier": "t_mapping$_t_uint128_$_t_uint256_$", + "typeString": "mapping(uint128 => uint256)" }, "valueName": "", "valueNameLocation": "-1:-1:-1", "valueType": { - "id": 29121, + "id": 29177, "name": "uint256", "nodeType": "ElementaryTypeName", - "src": "134:7:17", + "src": "134:7:19", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" @@ -228,56 +266,56 @@ "visibility": "internal" }, { - "id": 29127, + "id": 29183, "nodeType": "VariableDeclaration", - "src": "165:26:17", + "src": "167:25:19", "nodes": [], "constant": false, "mutability": "mutable", "name": "numbers", - "nameLocation": "184:7:17", - "scope": 29156, + "nameLocation": "185:7:19", + "scope": 29224, "stateVariable": true, "storageLocation": "default", "typeDescriptions": { - "typeIdentifier": "t_array$_t_uint256_$dyn_storage", - "typeString": "uint256[]" + "typeIdentifier": "t_array$_t_uint64_$dyn_storage", + "typeString": "uint64[]" }, "typeName": { "baseType": { - "id": 29125, - "name": "uint256", + "id": 29181, + "name": "uint64", "nodeType": "ElementaryTypeName", - "src": "165:7:17", + "src": "167:6:19", "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" + "typeIdentifier": "t_uint64", + "typeString": "uint64" } }, - "id": 29126, + "id": 29182, "nodeType": "ArrayTypeName", - "src": "165:9:17", + "src": "167:8:19", "typeDescriptions": { - "typeIdentifier": "t_array$_t_uint256_$dyn_storage_ptr", - "typeString": "uint256[]" + "typeIdentifier": "t_array$_t_uint64_$dyn_storage_ptr", + "typeString": "uint64[]" } }, "visibility": "internal" }, { - "id": 29143, + "id": 29199, "nodeType": "FunctionDefinition", - "src": "198:89:17", + "src": "199:91:19", "nodes": [], "body": { - "id": 29142, + "id": 29198, "nodeType": "Block", - "src": "246:41:17", + "src": "247:43:19", "nodes": [], "statements": [ { "expression": { - "id": 29140, + "id": 29196, "isConstant": false, "isLValue": false, "isPure": false, @@ -285,28 +323,28 @@ "leftHandSide": { "baseExpression": { "baseExpression": { - "id": 29134, - "name": "number", + "id": 29190, + "name": "mappings", "nodeType": "Identifier", "overloadedDeclarations": [], - "referencedDeclaration": 29124, - "src": "256:6:17", + "referencedDeclaration": 29180, + "src": "257:8:19", "typeDescriptions": { - "typeIdentifier": "t_mapping$_t_uint256_$_t_mapping$_t_uint256_$_t_uint256_$_$", - "typeString": "mapping(uint256 => mapping(uint256 => uint256))" + "typeIdentifier": "t_mapping$_t_uint128_$_t_mapping$_t_uint128_$_t_uint256_$_$", + "typeString": "mapping(uint128 => mapping(uint128 => uint256))" } }, - "id": 29137, + "id": 29193, "indexExpression": { - "id": 29135, + "id": 29191, "name": "key", "nodeType": "Identifier", "overloadedDeclarations": [], - "referencedDeclaration": 29129, - "src": "263:3:17", + "referencedDeclaration": 29185, + "src": "266:3:19", "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" + "typeIdentifier": "t_uint128", + "typeString": "uint128" } }, "isConstant": false, @@ -314,23 +352,23 @@ "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", - "src": "256:11:17", + "src": "257:13:19", "typeDescriptions": { - "typeIdentifier": "t_mapping$_t_uint256_$_t_uint256_$", - "typeString": "mapping(uint256 => uint256)" + "typeIdentifier": "t_mapping$_t_uint128_$_t_uint256_$", + "typeString": "mapping(uint128 => uint256)" } }, - "id": 29138, + "id": 29194, "indexExpression": { - "id": 29136, + "id": 29192, "name": "key", "nodeType": "Identifier", "overloadedDeclarations": [], - "referencedDeclaration": 29129, - "src": "268:3:17", + "referencedDeclaration": 29185, + "src": "271:3:19", "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" + "typeIdentifier": "t_uint128", + "typeString": "uint128" } }, "isConstant": false, @@ -338,7 +376,7 @@ "isPure": false, "lValueRequested": true, "nodeType": "IndexAccess", - "src": "256:16:17", + "src": "257:18:19", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" @@ -347,75 +385,75 @@ "nodeType": "Assignment", "operator": "=", "rightHandSide": { - "id": 29139, + "id": 29195, "name": "value", "nodeType": "Identifier", "overloadedDeclarations": [], - "referencedDeclaration": 29131, - "src": "275:5:17", + "referencedDeclaration": 29187, + "src": "278:5:19", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, - "src": "256:24:17", + "src": "257:26:19", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, - "id": 29141, + "id": 29197, "nodeType": "ExpressionStatement", - "src": "256:24:17" + "src": "257:26:19" } ] }, - "functionSelector": "771602f7", + "functionSelector": "871d7573", "implemented": true, "kind": "function", "modifiers": [], "name": "add", - "nameLocation": "207:3:17", + "nameLocation": "208:3:19", "parameters": { - "id": 29132, + "id": 29188, "nodeType": "ParameterList", "parameters": [ { "constant": false, - "id": 29129, + "id": 29185, "mutability": "mutable", "name": "key", - "nameLocation": "219:3:17", + "nameLocation": "220:3:19", "nodeType": "VariableDeclaration", - "scope": 29143, - "src": "211:11:17", + "scope": 29199, + "src": "212:11:19", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" + "typeIdentifier": "t_uint128", + "typeString": "uint128" }, "typeName": { - "id": 29128, - "name": "uint256", + "id": 29184, + "name": "uint128", "nodeType": "ElementaryTypeName", - "src": "211:7:17", + "src": "212:7:19", "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" + "typeIdentifier": "t_uint128", + "typeString": "uint128" } }, "visibility": "internal" }, { "constant": false, - "id": 29131, + "id": 29187, "mutability": "mutable", "name": "value", - "nameLocation": "232:5:17", + "nameLocation": "233:5:19", "nodeType": "VariableDeclaration", - "scope": 29143, - "src": "224:13:17", + "scope": 29199, + "src": "225:13:19", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { @@ -423,10 +461,10 @@ "typeString": "uint256" }, "typeName": { - "id": 29130, + "id": 29186, "name": "uint256", "nodeType": "ElementaryTypeName", - "src": "224:7:17", + "src": "225:7:19", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" @@ -435,80 +473,80 @@ "visibility": "internal" } ], - "src": "210:28:17" + "src": "211:28:19" }, "returnParameters": { - "id": 29133, + "id": 29189, "nodeType": "ParameterList", "parameters": [], - "src": "246:0:17" + "src": "247:0:19" }, - "scope": 29156, + "scope": 29224, "stateMutability": "nonpayable", "virtual": false, "visibility": "public" }, { - "id": 29155, + "id": 29211, "nodeType": "FunctionDefinition", - "src": "293:76:17", + "src": "296:75:19", "nodes": [], "body": { - "id": 29154, + "id": 29210, "nodeType": "Block", - "src": "332:37:17", + "src": "334:37:19", "nodes": [], "statements": [ { "expression": { "arguments": [ { - "id": 29151, + "id": 29207, "name": "number", "nodeType": "Identifier", "overloadedDeclarations": [], - "referencedDeclaration": 29145, - "src": "355:6:17", + "referencedDeclaration": 29201, + "src": "357:6:19", "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" + "typeIdentifier": "t_uint64", + "typeString": "uint64" } } ], "expression": { "argumentTypes": [ { - "typeIdentifier": "t_uint256", - "typeString": "uint256" + "typeIdentifier": "t_uint64", + "typeString": "uint64" } ], "expression": { - "id": 29148, + "id": 29204, "name": "numbers", "nodeType": "Identifier", "overloadedDeclarations": [], - "referencedDeclaration": 29127, - "src": "342:7:17", + "referencedDeclaration": 29183, + "src": "344:7:19", "typeDescriptions": { - "typeIdentifier": "t_array$_t_uint256_$dyn_storage", - "typeString": "uint256[] storage ref" + "typeIdentifier": "t_array$_t_uint64_$dyn_storage", + "typeString": "uint64[] storage ref" } }, - "id": 29150, + "id": 29206, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, - "memberLocation": "350:4:17", + "memberLocation": "352:4:19", "memberName": "push", "nodeType": "MemberAccess", - "src": "342:12:17", + "src": "344:12:19", "typeDescriptions": { - "typeIdentifier": "t_function_arraypush_nonpayable$_t_array$_t_uint256_$dyn_storage_ptr_$_t_uint256_$returns$__$attached_to$_t_array$_t_uint256_$dyn_storage_ptr_$", - "typeString": "function (uint256[] storage pointer,uint256)" + "typeIdentifier": "t_function_arraypush_nonpayable$_t_array$_t_uint64_$dyn_storage_ptr_$_t_uint64_$returns$__$attached_to$_t_array$_t_uint64_$dyn_storage_ptr_$", + "typeString": "function (uint64[] storage pointer,uint64)" } }, - "id": 29152, + "id": 29208, "isConstant": false, "isLValue": false, "isPure": false, @@ -517,38 +555,145 @@ "nameLocations": [], "names": [], "nodeType": "FunctionCall", - "src": "342:20:17", + "src": "344:20:19", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, - "id": 29153, + "id": 29209, "nodeType": "ExpressionStatement", - "src": "342:20:17" + "src": "344:20:19" } ] }, - "functionSelector": "e33b8707", + "functionSelector": "3b076df4", "implemented": true, "kind": "function", "modifiers": [], "name": "append", - "nameLocation": "302:6:17", + "nameLocation": "305:6:19", "parameters": { - "id": 29146, + "id": 29202, "nodeType": "ParameterList", "parameters": [ { "constant": false, - "id": 29145, + "id": 29201, "mutability": "mutable", "name": "number", - "nameLocation": "317:6:17", + "nameLocation": "319:6:19", "nodeType": "VariableDeclaration", - "scope": 29155, - "src": "309:14:17", + "scope": 29211, + "src": "312:13:19", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + }, + "typeName": { + "id": 29200, + "name": "uint64", + "nodeType": "ElementaryTypeName", + "src": "312:6:19", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + } + }, + "visibility": "internal" + } + ], + "src": "311:15:19" + }, + "returnParameters": { + "id": 29203, + "nodeType": "ParameterList", + "parameters": [], + "src": "334:0:19" + }, + "scope": 29224, + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "id": 29223, + "nodeType": "FunctionDefinition", + "src": "377:96:19", + "nodes": [], + "body": { + "id": 29222, + "nodeType": "Block", + "src": "435:38:19", + "nodes": [], + "statements": [ + { + "expression": { + "baseExpression": { + "id": 29218, + "name": "numbers", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 29183, + "src": "452:7:19", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_uint64_$dyn_storage", + "typeString": "uint64[] storage ref" + } + }, + "id": 29220, + "indexExpression": { + "id": 29219, + "name": "index", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 29213, + "src": "460:5:19", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "452:14:19", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + } + }, + "functionReturnParameters": 29217, + "id": 29221, + "nodeType": "Return", + "src": "445:21:19" + } + ] + }, + "functionSelector": "ed2e5a97", + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "read", + "nameLocation": "386:4:19", + "parameters": { + "id": 29214, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 29213, + "mutability": "mutable", + "name": "index", + "nameLocation": "399:5:19", + "nodeType": "VariableDeclaration", + "scope": 29223, + "src": "391:13:19", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { @@ -556,10 +701,10 @@ "typeString": "uint256" }, "typeName": { - "id": 29144, + "id": 29212, "name": "uint256", "nodeType": "ElementaryTypeName", - "src": "309:7:17", + "src": "391:7:19", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" @@ -568,16 +713,44 @@ "visibility": "internal" } ], - "src": "308:16:17" + "src": "390:15:19" }, "returnParameters": { - "id": 29147, + "id": 29217, "nodeType": "ParameterList", - "parameters": [], - "src": "332:0:17" + "parameters": [ + { + "constant": false, + "id": 29216, + "mutability": "mutable", + "name": "", + "nameLocation": "-1:-1:-1", + "nodeType": "VariableDeclaration", + "scope": 29223, + "src": "427:6:19", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + }, + "typeName": { + "id": 29215, + "name": "uint64", + "nodeType": "ElementaryTypeName", + "src": "427:6:19", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + } + }, + "visibility": "internal" + } + ], + "src": "426:8:19" }, - "scope": 29156, - "stateMutability": "nonpayable", + "scope": 29224, + "stateMutability": "view", "virtual": false, "visibility": "public" } @@ -589,15 +762,15 @@ "contractKind": "contract", "fullyImplemented": true, "linearizedBaseContracts": [ - 29156 + 29224 ], "name": "BytecodeExample", - "nameLocation": "74:15:17", - "scope": 29157, + "nameLocation": "74:15:19", + "scope": 29225, "usedErrors": [] } ], "license": "UNLICENSED" }, - "id": 17 + "id": 19 } \ No newline at end of file diff --git a/asset/ReplaceMeForTesting.sol b/asset/ReplaceMeForTesting.sol index f12e766..9adcc11 100644 --- a/asset/ReplaceMeForTesting.sol +++ b/asset/ReplaceMeForTesting.sol @@ -2,14 +2,18 @@ pragma solidity ^0.8.13; contract BytecodeExample { - mapping(uint256 => mapping(uint256 => uint256)) internal number; - uint256[] internal numbers; + mapping(uint128 => mapping(uint128 => uint256)) internal mappings; + uint64[] internal numbers; - function add(uint256 key, uint256 value) public { - number[key][key] = value; + function add(uint128 key, uint256 value) public { + mappings[key][key] = value; } - function append(uint256 number) public { + function append(uint64 number) public { numbers.push(number); } + + function read(uint256 index) public view returns (uint64) { + return numbers[index]; + } } diff --git a/asset/SimpleContract.json b/asset/SimpleContract.json index adddc9d..ebbf9c3 100644 --- a/asset/SimpleContract.json +++ b/asset/SimpleContract.json @@ -3,9 +3,9 @@ { "inputs": [ { - "internalType": "uint256", + "internalType": "uint128", "name": "key", - "type": "uint256" + "type": "uint128" }, { "internalType": "uint256", @@ -21,32 +21,52 @@ { "inputs": [ { - "internalType": "uint256", + "internalType": "uint64", "name": "number", - "type": "uint256" + "type": "uint64" } ], "name": "append", "outputs": [], "stateMutability": "nonpayable", "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "read", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" } ], "bytecode": { - "object": "0x608060405234801561001057600080fd5b5060d48061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c8063771602f7146037578063e33b870714605c575b600080fd5b605a6042366004609b565b60009182526020828152604080842090915290912055565b005b605a606736600460bc565b6001805480820182556000919091527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf60155565b6000806040838503121560ad57600080fd5b50508035926020909101359150565b60006020828403121560cd57600080fd5b503591905056", - "sourceMap": "65:306:17:-:0;;;;;;;;;;;;;;;;;;;", + "object": "0x608060405234801561001057600080fd5b5061022d806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80633b076df414610046578063871d7573146100b9578063ed2e5a97146100e9575b600080fd5b6100b761005436600461015f565b6001805480820182556000919091527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf66004820401805460039092166008026101000a67ffffffffffffffff818102199093169390921691909102919091179055565b005b6100b76100c7366004610190565b6001600160801b03909116600090815260208181526040808320909152902055565b6100fc6100f73660046101c8565b610119565b60405167ffffffffffffffff909116815260200160405180910390f35b60006001828154811061012e5761012e6101e1565b90600052602060002090600491828204019190066008029054906101000a900467ffffffffffffffff169050919050565b60006020828403121561017157600080fd5b813567ffffffffffffffff8116811461018957600080fd5b9392505050565b600080604083850312156101a357600080fd5b82356001600160801b03811681146101ba57600080fd5b946020939093013593505050565b6000602082840312156101da57600080fd5b5035919050565b634e487b7160e01b600052603260045260246000fdfea26469706673582212206e123127fa4e1b84a05360ff2312642e7eca667c5e4bb91600d5fadf7744889064736f6c63430008130033", + "sourceMap": "65:410:19:-:0;;;;;;;;;;;;;;;;;;;", "linkReferences": {} }, "deployedBytecode": { - "object": "0x6080604052348015600f57600080fd5b506004361060325760003560e01c8063771602f7146037578063e33b870714605c575b600080fd5b605a6042366004609b565b60009182526020828152604080842090915290912055565b005b605a606736600460bc565b6001805480820182556000919091527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf60155565b6000806040838503121560ad57600080fd5b50508035926020909101359150565b60006020828403121560cd57600080fd5b503591905056", - "sourceMap": "65:306:17:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;198:89;;;;;;:::i;:::-;256:6;:11;;;;;;;;;;;:16;;;;;;:24;198:89;;;293:76;;;;;;:::i;:::-;342:7;:20;;;;;;;-1:-1:-1;342:20:17;;;;;;;293:76;14:248:19;82:6;90;143:2;131:9;122:7;118:23;114:32;111:52;;;159:1;156;149:12;111:52;-1:-1:-1;;182:23:19;;;252:2;237:18;;;224:32;;-1:-1:-1;14:248:19:o;267:180::-;326:6;379:2;367:9;358:7;354:23;350:32;347:52;;;395:1;392;385:12;347:52;-1:-1:-1;418:23:19;;267:180;-1:-1:-1;267:180:19:o", + "object": "0x608060405234801561001057600080fd5b50600436106100415760003560e01c80633b076df414610046578063871d7573146100b9578063ed2e5a97146100e9575b600080fd5b6100b761005436600461015f565b6001805480820182556000919091527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf66004820401805460039092166008026101000a67ffffffffffffffff818102199093169390921691909102919091179055565b005b6100b76100c7366004610190565b6001600160801b03909116600090815260208181526040808320909152902055565b6100fc6100f73660046101c8565b610119565b60405167ffffffffffffffff909116815260200160405180910390f35b60006001828154811061012e5761012e6101e1565b90600052602060002090600491828204019190066008029054906101000a900467ffffffffffffffff169050919050565b60006020828403121561017157600080fd5b813567ffffffffffffffff8116811461018957600080fd5b9392505050565b600080604083850312156101a357600080fd5b82356001600160801b03811681146101ba57600080fd5b946020939093013593505050565b6000602082840312156101da57600080fd5b5035919050565b634e487b7160e01b600052603260045260246000fdfea26469706673582212206e123127fa4e1b84a05360ff2312642e7eca667c5e4bb91600d5fadf7744889064736f6c63430008130033", + "sourceMap": "65:410:19:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;296:75;;;;;;:::i;:::-;344:7;:20;;;;;;;-1:-1:-1;344:20:19;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;296:75;;;199:91;;;;;;:::i;:::-;-1:-1:-1;;;;;257:13:19;;;:8;:13;;;;;;;;;;;:18;;;;;:26;199:91;377:96;;;;;;:::i;:::-;;:::i;:::-;;;1036:18:21;1024:31;;;1006:50;;994:2;979:18;377:96:19;;;;;;;;427:6;452:7;460:5;452:14;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;445:21;;377:96;;;:::o;14:284:21:-;72:6;125:2;113:9;104:7;100:23;96:32;93:52;;;141:1;138;131:12;93:52;180:9;167:23;230:18;223:5;219:30;212:5;209:41;199:69;;264:1;261;254:12;199:69;287:5;14:284;-1:-1:-1;;;14:284:21:o;303:369::-;371:6;379;432:2;420:9;411:7;407:23;403:32;400:52;;;448:1;445;438:12;400:52;487:9;474:23;-1:-1:-1;;;;;530:5:21;526:46;519:5;516:57;506:85;;587:1;584;577:12;506:85;610:5;662:2;647:18;;;;634:32;;-1:-1:-1;;;303:369:21:o;677:180::-;736:6;789:2;777:9;768:7;764:23;760:32;757:52;;;805:1;802;795:12;757:52;-1:-1:-1;828:23:21;;677:180;-1:-1:-1;677:180:21:o;1067:127::-;1128:10;1123:3;1119:20;1116:1;1109:31;1159:4;1156:1;1149:15;1183:4;1180:1;1173:15", "linkReferences": {} }, "methodIdentifiers": { - "add(uint256,uint256)": "771602f7", - "append(uint256)": "e33b8707" + "add(uint128,uint256)": "871d7573", + "append(uint64)": "3b076df4", + "read(uint256)": "ed2e5a97" }, - "rawMetadata": "{\"compiler\":{\"version\":\"0.8.19+commit.7dd6d404\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"key\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"add\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"number\",\"type\":\"uint256\"}],\"name\":\"append\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/BytecodeExample.sol\":\"BytecodeExample\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"appendCBOR\":false,\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":ds-test/=lib/forge-std/lib/ds-test/src/\",\":forge-std/=lib/forge-std/src/\"]},\"sources\":{\"src/BytecodeExample.sol\":{\"keccak256\":\"0x6e171e6e9b0498b21224a8cb15e7bb1c459a828986303e029052de8f586597db\",\"license\":\"UNLICENSED\",\"urls\":[\"bzz-raw://58fd0ec6be47112ba3671fdfd7a6a39774894fe0e314740fd5aecc87cca92115\",\"dweb:/ipfs/QmTgRhkdWUjvBEUoho4eGuGgagwcg5FHWKfAEcoJBKny3h\"]}},\"version\":1}", + "rawMetadata": "{\"compiler\":{\"version\":\"0.8.19+commit.7dd6d404\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"uint128\",\"name\":\"key\",\"type\":\"uint128\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"add\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"number\",\"type\":\"uint64\"}],\"name\":\"append\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"}],\"name\":\"read\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/BytecodeExample.sol\":\"BytecodeExample\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":ds-test/=lib/forge-std/lib/ds-test/src/\",\":forge-std/=lib/forge-std/src/\"]},\"sources\":{\"src/BytecodeExample.sol\":{\"keccak256\":\"0x2adb29206e84ee9eea1c3477f5e4b88804ea0e08287ce7c34d9e71a45b73c04f\",\"license\":\"UNLICENSED\",\"urls\":[\"bzz-raw://f01c75aaeedab5e22c8cd6439da0e844e0c7deeea6af22464f25f801e15df55e\",\"dweb:/ipfs/QmVR6apvqZxt6qpcCGHxe6HKBS9XDuMCJdZNvrduPpVy7J\"]}},\"version\":1}", "metadata": { "compiler": { "version": "0.8.19+commit.7dd6d404" @@ -57,9 +77,9 @@ { "inputs": [ { - "internalType": "uint256", + "internalType": "uint128", "name": "key", - "type": "uint256" + "type": "uint128" }, { "internalType": "uint256", @@ -74,14 +94,33 @@ { "inputs": [ { - "internalType": "uint256", + "internalType": "uint64", "name": "number", - "type": "uint256" + "type": "uint64" } ], "stateMutability": "nonpayable", "type": "function", "name": "append" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function", + "name": "read", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ] } ], "devdoc": { @@ -105,8 +144,7 @@ "runs": 200 }, "metadata": { - "bytecodeHash": "none", - "appendCBOR": false + "bytecodeHash": "ipfs" }, "compilationTarget": { "src/BytecodeExample.sol": "BytecodeExample" @@ -115,10 +153,10 @@ }, "sources": { "src/BytecodeExample.sol": { - "keccak256": "0x6e171e6e9b0498b21224a8cb15e7bb1c459a828986303e029052de8f586597db", + "keccak256": "0x2adb29206e84ee9eea1c3477f5e4b88804ea0e08287ce7c34d9e71a45b73c04f", "urls": [ - "bzz-raw://58fd0ec6be47112ba3671fdfd7a6a39774894fe0e314740fd5aecc87cca92115", - "dweb:/ipfs/QmTgRhkdWUjvBEUoho4eGuGgagwcg5FHWKfAEcoJBKny3h" + "bzz-raw://f01c75aaeedab5e22c8cd6439da0e844e0c7deeea6af22464f25f801e15df55e", + "dweb:/ipfs/QmVR6apvqZxt6qpcCGHxe6HKBS9XDuMCJdZNvrduPpVy7J" ], "license": "UNLICENSED" } @@ -127,19 +165,19 @@ }, "ast": { "absolutePath": "src/BytecodeExample.sol", - "id": 29157, + "id": 29225, "exportedSymbols": { "BytecodeExample": [ - 29156 + 29224 ] }, "nodeType": "SourceUnit", - "src": "39:333:17", + "src": "39:437:19", "nodes": [ { - "id": 29118, + "id": 29174, "nodeType": "PragmaDirective", - "src": "39:24:17", + "src": "39:24:19", "nodes": [], "literals": [ "solidity", @@ -149,75 +187,75 @@ ] }, { - "id": 29156, + "id": 29224, "nodeType": "ContractDefinition", - "src": "65:306:17", + "src": "65:410:19", "nodes": [ { - "id": 29124, + "id": 29180, "nodeType": "VariableDeclaration", - "src": "96:63:17", + "src": "96:65:19", "nodes": [], "constant": false, "mutability": "mutable", - "name": "number", - "nameLocation": "153:6:17", - "scope": 29156, + "name": "mappings", + "nameLocation": "153:8:19", + "scope": 29224, "stateVariable": true, "storageLocation": "default", "typeDescriptions": { - "typeIdentifier": "t_mapping$_t_uint256_$_t_mapping$_t_uint256_$_t_uint256_$_$", - "typeString": "mapping(uint256 => mapping(uint256 => uint256))" + "typeIdentifier": "t_mapping$_t_uint128_$_t_mapping$_t_uint128_$_t_uint256_$_$", + "typeString": "mapping(uint128 => mapping(uint128 => uint256))" }, "typeName": { - "id": 29123, + "id": 29179, "keyName": "", "keyNameLocation": "-1:-1:-1", "keyType": { - "id": 29119, - "name": "uint256", + "id": 29175, + "name": "uint128", "nodeType": "ElementaryTypeName", - "src": "104:7:17", + "src": "104:7:19", "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" + "typeIdentifier": "t_uint128", + "typeString": "uint128" } }, "nodeType": "Mapping", - "src": "96:47:17", + "src": "96:47:19", "typeDescriptions": { - "typeIdentifier": "t_mapping$_t_uint256_$_t_mapping$_t_uint256_$_t_uint256_$_$", - "typeString": "mapping(uint256 => mapping(uint256 => uint256))" + "typeIdentifier": "t_mapping$_t_uint128_$_t_mapping$_t_uint128_$_t_uint256_$_$", + "typeString": "mapping(uint128 => mapping(uint128 => uint256))" }, "valueName": "", "valueNameLocation": "-1:-1:-1", "valueType": { - "id": 29122, + "id": 29178, "keyName": "", "keyNameLocation": "-1:-1:-1", "keyType": { - "id": 29120, - "name": "uint256", + "id": 29176, + "name": "uint128", "nodeType": "ElementaryTypeName", - "src": "123:7:17", + "src": "123:7:19", "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" + "typeIdentifier": "t_uint128", + "typeString": "uint128" } }, "nodeType": "Mapping", - "src": "115:27:17", + "src": "115:27:19", "typeDescriptions": { - "typeIdentifier": "t_mapping$_t_uint256_$_t_uint256_$", - "typeString": "mapping(uint256 => uint256)" + "typeIdentifier": "t_mapping$_t_uint128_$_t_uint256_$", + "typeString": "mapping(uint128 => uint256)" }, "valueName": "", "valueNameLocation": "-1:-1:-1", "valueType": { - "id": 29121, + "id": 29177, "name": "uint256", "nodeType": "ElementaryTypeName", - "src": "134:7:17", + "src": "134:7:19", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" @@ -228,56 +266,56 @@ "visibility": "internal" }, { - "id": 29127, + "id": 29183, "nodeType": "VariableDeclaration", - "src": "165:26:17", + "src": "167:25:19", "nodes": [], "constant": false, "mutability": "mutable", "name": "numbers", - "nameLocation": "184:7:17", - "scope": 29156, + "nameLocation": "185:7:19", + "scope": 29224, "stateVariable": true, "storageLocation": "default", "typeDescriptions": { - "typeIdentifier": "t_array$_t_uint256_$dyn_storage", - "typeString": "uint256[]" + "typeIdentifier": "t_array$_t_uint64_$dyn_storage", + "typeString": "uint64[]" }, "typeName": { "baseType": { - "id": 29125, - "name": "uint256", + "id": 29181, + "name": "uint64", "nodeType": "ElementaryTypeName", - "src": "165:7:17", + "src": "167:6:19", "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" + "typeIdentifier": "t_uint64", + "typeString": "uint64" } }, - "id": 29126, + "id": 29182, "nodeType": "ArrayTypeName", - "src": "165:9:17", + "src": "167:8:19", "typeDescriptions": { - "typeIdentifier": "t_array$_t_uint256_$dyn_storage_ptr", - "typeString": "uint256[]" + "typeIdentifier": "t_array$_t_uint64_$dyn_storage_ptr", + "typeString": "uint64[]" } }, "visibility": "internal" }, { - "id": 29143, + "id": 29199, "nodeType": "FunctionDefinition", - "src": "198:89:17", + "src": "199:91:19", "nodes": [], "body": { - "id": 29142, + "id": 29198, "nodeType": "Block", - "src": "246:41:17", + "src": "247:43:19", "nodes": [], "statements": [ { "expression": { - "id": 29140, + "id": 29196, "isConstant": false, "isLValue": false, "isPure": false, @@ -285,28 +323,28 @@ "leftHandSide": { "baseExpression": { "baseExpression": { - "id": 29134, - "name": "number", + "id": 29190, + "name": "mappings", "nodeType": "Identifier", "overloadedDeclarations": [], - "referencedDeclaration": 29124, - "src": "256:6:17", + "referencedDeclaration": 29180, + "src": "257:8:19", "typeDescriptions": { - "typeIdentifier": "t_mapping$_t_uint256_$_t_mapping$_t_uint256_$_t_uint256_$_$", - "typeString": "mapping(uint256 => mapping(uint256 => uint256))" + "typeIdentifier": "t_mapping$_t_uint128_$_t_mapping$_t_uint128_$_t_uint256_$_$", + "typeString": "mapping(uint128 => mapping(uint128 => uint256))" } }, - "id": 29137, + "id": 29193, "indexExpression": { - "id": 29135, + "id": 29191, "name": "key", "nodeType": "Identifier", "overloadedDeclarations": [], - "referencedDeclaration": 29129, - "src": "263:3:17", + "referencedDeclaration": 29185, + "src": "266:3:19", "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" + "typeIdentifier": "t_uint128", + "typeString": "uint128" } }, "isConstant": false, @@ -314,23 +352,23 @@ "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", - "src": "256:11:17", + "src": "257:13:19", "typeDescriptions": { - "typeIdentifier": "t_mapping$_t_uint256_$_t_uint256_$", - "typeString": "mapping(uint256 => uint256)" + "typeIdentifier": "t_mapping$_t_uint128_$_t_uint256_$", + "typeString": "mapping(uint128 => uint256)" } }, - "id": 29138, + "id": 29194, "indexExpression": { - "id": 29136, + "id": 29192, "name": "key", "nodeType": "Identifier", "overloadedDeclarations": [], - "referencedDeclaration": 29129, - "src": "268:3:17", + "referencedDeclaration": 29185, + "src": "271:3:19", "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" + "typeIdentifier": "t_uint128", + "typeString": "uint128" } }, "isConstant": false, @@ -338,7 +376,7 @@ "isPure": false, "lValueRequested": true, "nodeType": "IndexAccess", - "src": "256:16:17", + "src": "257:18:19", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" @@ -347,75 +385,75 @@ "nodeType": "Assignment", "operator": "=", "rightHandSide": { - "id": 29139, + "id": 29195, "name": "value", "nodeType": "Identifier", "overloadedDeclarations": [], - "referencedDeclaration": 29131, - "src": "275:5:17", + "referencedDeclaration": 29187, + "src": "278:5:19", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, - "src": "256:24:17", + "src": "257:26:19", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, - "id": 29141, + "id": 29197, "nodeType": "ExpressionStatement", - "src": "256:24:17" + "src": "257:26:19" } ] }, - "functionSelector": "771602f7", + "functionSelector": "871d7573", "implemented": true, "kind": "function", "modifiers": [], "name": "add", - "nameLocation": "207:3:17", + "nameLocation": "208:3:19", "parameters": { - "id": 29132, + "id": 29188, "nodeType": "ParameterList", "parameters": [ { "constant": false, - "id": 29129, + "id": 29185, "mutability": "mutable", "name": "key", - "nameLocation": "219:3:17", + "nameLocation": "220:3:19", "nodeType": "VariableDeclaration", - "scope": 29143, - "src": "211:11:17", + "scope": 29199, + "src": "212:11:19", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" + "typeIdentifier": "t_uint128", + "typeString": "uint128" }, "typeName": { - "id": 29128, - "name": "uint256", + "id": 29184, + "name": "uint128", "nodeType": "ElementaryTypeName", - "src": "211:7:17", + "src": "212:7:19", "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" + "typeIdentifier": "t_uint128", + "typeString": "uint128" } }, "visibility": "internal" }, { "constant": false, - "id": 29131, + "id": 29187, "mutability": "mutable", "name": "value", - "nameLocation": "232:5:17", + "nameLocation": "233:5:19", "nodeType": "VariableDeclaration", - "scope": 29143, - "src": "224:13:17", + "scope": 29199, + "src": "225:13:19", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { @@ -423,10 +461,10 @@ "typeString": "uint256" }, "typeName": { - "id": 29130, + "id": 29186, "name": "uint256", "nodeType": "ElementaryTypeName", - "src": "224:7:17", + "src": "225:7:19", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" @@ -435,80 +473,80 @@ "visibility": "internal" } ], - "src": "210:28:17" + "src": "211:28:19" }, "returnParameters": { - "id": 29133, + "id": 29189, "nodeType": "ParameterList", "parameters": [], - "src": "246:0:17" + "src": "247:0:19" }, - "scope": 29156, + "scope": 29224, "stateMutability": "nonpayable", "virtual": false, "visibility": "public" }, { - "id": 29155, + "id": 29211, "nodeType": "FunctionDefinition", - "src": "293:76:17", + "src": "296:75:19", "nodes": [], "body": { - "id": 29154, + "id": 29210, "nodeType": "Block", - "src": "332:37:17", + "src": "334:37:19", "nodes": [], "statements": [ { "expression": { "arguments": [ { - "id": 29151, + "id": 29207, "name": "number", "nodeType": "Identifier", "overloadedDeclarations": [], - "referencedDeclaration": 29145, - "src": "355:6:17", + "referencedDeclaration": 29201, + "src": "357:6:19", "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" + "typeIdentifier": "t_uint64", + "typeString": "uint64" } } ], "expression": { "argumentTypes": [ { - "typeIdentifier": "t_uint256", - "typeString": "uint256" + "typeIdentifier": "t_uint64", + "typeString": "uint64" } ], "expression": { - "id": 29148, + "id": 29204, "name": "numbers", "nodeType": "Identifier", "overloadedDeclarations": [], - "referencedDeclaration": 29127, - "src": "342:7:17", + "referencedDeclaration": 29183, + "src": "344:7:19", "typeDescriptions": { - "typeIdentifier": "t_array$_t_uint256_$dyn_storage", - "typeString": "uint256[] storage ref" + "typeIdentifier": "t_array$_t_uint64_$dyn_storage", + "typeString": "uint64[] storage ref" } }, - "id": 29150, + "id": 29206, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, - "memberLocation": "350:4:17", + "memberLocation": "352:4:19", "memberName": "push", "nodeType": "MemberAccess", - "src": "342:12:17", + "src": "344:12:19", "typeDescriptions": { - "typeIdentifier": "t_function_arraypush_nonpayable$_t_array$_t_uint256_$dyn_storage_ptr_$_t_uint256_$returns$__$attached_to$_t_array$_t_uint256_$dyn_storage_ptr_$", - "typeString": "function (uint256[] storage pointer,uint256)" + "typeIdentifier": "t_function_arraypush_nonpayable$_t_array$_t_uint64_$dyn_storage_ptr_$_t_uint64_$returns$__$attached_to$_t_array$_t_uint64_$dyn_storage_ptr_$", + "typeString": "function (uint64[] storage pointer,uint64)" } }, - "id": 29152, + "id": 29208, "isConstant": false, "isLValue": false, "isPure": false, @@ -517,38 +555,145 @@ "nameLocations": [], "names": [], "nodeType": "FunctionCall", - "src": "342:20:17", + "src": "344:20:19", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, - "id": 29153, + "id": 29209, "nodeType": "ExpressionStatement", - "src": "342:20:17" + "src": "344:20:19" } ] }, - "functionSelector": "e33b8707", + "functionSelector": "3b076df4", "implemented": true, "kind": "function", "modifiers": [], "name": "append", - "nameLocation": "302:6:17", + "nameLocation": "305:6:19", "parameters": { - "id": 29146, + "id": 29202, "nodeType": "ParameterList", "parameters": [ { "constant": false, - "id": 29145, + "id": 29201, "mutability": "mutable", "name": "number", - "nameLocation": "317:6:17", + "nameLocation": "319:6:19", "nodeType": "VariableDeclaration", - "scope": 29155, - "src": "309:14:17", + "scope": 29211, + "src": "312:13:19", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + }, + "typeName": { + "id": 29200, + "name": "uint64", + "nodeType": "ElementaryTypeName", + "src": "312:6:19", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + } + }, + "visibility": "internal" + } + ], + "src": "311:15:19" + }, + "returnParameters": { + "id": 29203, + "nodeType": "ParameterList", + "parameters": [], + "src": "334:0:19" + }, + "scope": 29224, + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "id": 29223, + "nodeType": "FunctionDefinition", + "src": "377:96:19", + "nodes": [], + "body": { + "id": 29222, + "nodeType": "Block", + "src": "435:38:19", + "nodes": [], + "statements": [ + { + "expression": { + "baseExpression": { + "id": 29218, + "name": "numbers", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 29183, + "src": "452:7:19", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_uint64_$dyn_storage", + "typeString": "uint64[] storage ref" + } + }, + "id": 29220, + "indexExpression": { + "id": 29219, + "name": "index", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 29213, + "src": "460:5:19", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "452:14:19", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + } + }, + "functionReturnParameters": 29217, + "id": 29221, + "nodeType": "Return", + "src": "445:21:19" + } + ] + }, + "functionSelector": "ed2e5a97", + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "read", + "nameLocation": "386:4:19", + "parameters": { + "id": 29214, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 29213, + "mutability": "mutable", + "name": "index", + "nameLocation": "399:5:19", + "nodeType": "VariableDeclaration", + "scope": 29223, + "src": "391:13:19", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { @@ -556,10 +701,10 @@ "typeString": "uint256" }, "typeName": { - "id": 29144, + "id": 29212, "name": "uint256", "nodeType": "ElementaryTypeName", - "src": "309:7:17", + "src": "391:7:19", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" @@ -568,16 +713,44 @@ "visibility": "internal" } ], - "src": "308:16:17" + "src": "390:15:19" }, "returnParameters": { - "id": 29147, + "id": 29217, "nodeType": "ParameterList", - "parameters": [], - "src": "332:0:17" + "parameters": [ + { + "constant": false, + "id": 29216, + "mutability": "mutable", + "name": "", + "nameLocation": "-1:-1:-1", + "nodeType": "VariableDeclaration", + "scope": 29223, + "src": "427:6:19", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + }, + "typeName": { + "id": 29215, + "name": "uint64", + "nodeType": "ElementaryTypeName", + "src": "427:6:19", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + } + }, + "visibility": "internal" + } + ], + "src": "426:8:19" }, - "scope": 29156, - "stateMutability": "nonpayable", + "scope": 29224, + "stateMutability": "view", "virtual": false, "visibility": "public" } @@ -589,15 +762,15 @@ "contractKind": "contract", "fullyImplemented": true, "linearizedBaseContracts": [ - 29156 + 29224 ], "name": "BytecodeExample", - "nameLocation": "74:15:17", - "scope": 29157, + "nameLocation": "74:15:19", + "scope": 29225, "usedErrors": [] } ], "license": "UNLICENSED" }, - "id": 17 + "id": 19 } \ No newline at end of file diff --git a/asset/SimpleContract.sol b/asset/SimpleContract.sol index f12e766..9adcc11 100644 --- a/asset/SimpleContract.sol +++ b/asset/SimpleContract.sol @@ -2,14 +2,18 @@ pragma solidity ^0.8.13; contract BytecodeExample { - mapping(uint256 => mapping(uint256 => uint256)) internal number; - uint256[] internal numbers; + mapping(uint128 => mapping(uint128 => uint256)) internal mappings; + uint64[] internal numbers; - function add(uint256 key, uint256 value) public { - number[key][key] = value; + function add(uint128 key, uint256 value) public { + mappings[key][key] = value; } - function append(uint256 number) public { + function append(uint64 number) public { numbers.push(number); } + + function read(uint256 index) public view returns (uint64) { + return numbers[index]; + } } diff --git a/docs/Extending the Analyzer.md b/docs/Extending the Analyzer.md index 40aa9e5..f50bcd8 100644 --- a/docs/Extending the Analyzer.md +++ b/docs/Extending the Analyzer.md @@ -119,7 +119,7 @@ s_store(storage_slot(dynamic_array[index]), value) equating - `d = dynamic_array` -- `f = word(unsigned, unknown width, unknown usage)` +- `f = word(width = unknown, usage = UnsignedWord)` - `b = g` ``` diff --git a/src/constant.rs b/src/constant.rs index 831f359..530ce92 100644 --- a/src/constant.rs +++ b/src/constant.rs @@ -41,7 +41,16 @@ pub const PUSH_OPCODE_MAX_BYTES: u8 = 32; pub const MAXIMUM_STACK_DEPTH: usize = 1024; /// The width of word on the EVM in bits. -pub const WORD_SIZE: usize = 256; +pub const WORD_SIZE_BITS: usize = 256; + +/// The width of a byte on the EVM (and most other places) in bits. +pub const BYTE_SIZE: usize = 8; + +/// The width of a word on the EVM in bytes. +pub const WORD_SIZE_BYTES: usize = WORD_SIZE_BITS / BYTE_SIZE; + +/// The bit-width of a bool type. +pub const BOOL_WIDTH_BITS: usize = BYTE_SIZE; /// The bit-width of an address type. pub const ADDRESS_WIDTH_BITS: usize = 160; @@ -54,3 +63,17 @@ pub const FUNCTION_WIDTH_BITS: usize = ADDRESS_WIDTH_BITS + SELECTOR_WIDTH_BITS; /// The default number of times that the virtual machine will visit each opcode. pub const DEFAULT_ITERATIONS_PER_OPCODE: usize = 10; + +/// The valid widths in bits of value types in solidity. +/// +/// These are defined by the language specification to have widths `8 <= N <= +/// 256` where `N % 8 == 0`. +pub const SOLIDITY_VALUE_TYPE_WIDTHS: [usize; WORD_SIZE_BYTES] = { + let mut array: [usize; WORD_SIZE_BYTES] = [0; WORD_SIZE_BYTES]; + let mut counter = 0usize; + while counter < WORD_SIZE_BYTES { + array[counter] = (counter + 1) * BYTE_SIZE; + counter += 1; + } + array +}; diff --git a/src/disassembly/disassembler.rs b/src/disassembly/disassembler.rs index 39c300e..2586871 100644 --- a/src/disassembly/disassembler.rs +++ b/src/disassembly/disassembler.rs @@ -58,6 +58,7 @@ pub fn disassemble(bytes: &[u8]) -> Result> { let mut opcodes: Vec = Vec::with_capacity(bytes.len()); let ops = &mut opcodes; + let mut last_push: u8 = 0; let mut last_push_start: u32 = 0; let mut push_size: u8 = 0; let mut push_size_bytes: u8 = push_size; @@ -92,6 +93,7 @@ pub fn disassemble(bytes: &[u8]) -> Result> { // Now we can zero out our state variables. push_bytes.clear(); push_size = 0; + last_push = 0; } } else { // Now we can match the next byte and process the opcode. @@ -162,6 +164,7 @@ pub fn disassemble(bytes: &[u8]) -> Result> { 0x5b => add_op(ops, control::JumpDest), 0x5f => add_op(ops, mem::Push0), 0x60..=0x7f => { + last_push = *byte; last_push_start = instruction_pointer; push_size = byte - PUSH_OPCODE_BASE_VALUE; push_size_bytes = push_size; @@ -202,6 +205,18 @@ pub fn disassemble(bytes: &[u8]) -> Result> { } } + // Solc has generated valid code that ends with an incomplete push, so we have + // to handle it by treating the unterminated push and all the subsequent bytes + // as invalid + if !push_bytes.is_empty() && push_bytes.len() != push_size as usize { + add_op(ops, control::Invalid::new(last_push)); + push_bytes.iter().for_each(|b| add_op(ops, control::Invalid::new(*b))); + } else if push_size != 0 { + let opcode = mem::PushN::new(push_size, push_bytes.clone()) + .map_err(|e| e.locate(last_push_start))?; + add_op(ops, opcode); + } + Ok(opcodes) } diff --git a/src/disassembly/mod.rs b/src/disassembly/mod.rs index 3343ce9..fb43d7c 100644 --- a/src/disassembly/mod.rs +++ b/src/disassembly/mod.rs @@ -110,9 +110,6 @@ impl<'a> TryFrom<&'a [u8]> for InstructionStream { fn try_from(value: &'a [u8]) -> Result { let instructions = Rc::new(disassembler::disassemble(value)?); - if instructions.len() > u32::MAX as usize { - return Err(Error::BytecodeTooLarge.locate(0)); - } let result = Self { instructions }; // An assertion that will be disabled in production builds, but a good sanity diff --git a/src/inference/abi.rs b/src/inference/abi.rs index e7952ca..e038eea 100644 --- a/src/inference/abi.rs +++ b/src/inference/abi.rs @@ -25,13 +25,20 @@ pub enum AbiType { /// is a concrete container but not of what type(s). Any, + /// A number of a given `size` in bits, where, `8 < size <= 256 && + /// size % 8 == 0`. + /// + /// This is emitted when the analyser knows that something has been used + /// numerically, but does not know whether it is concretely signed or not. + Number { size: Option }, + /// Unsigned integers of a given `size` in bits, where `8 < size <= 256 && /// size % 8 == 0`. - UInt { size: u16 }, + UInt { size: Option }, /// Signed (two's complement) integers of a given `size` in bits, where `8 < /// size <= 256 && size % 8 == 0`. - Int { size: u16 }, + Int { size: Option }, /// Addresses, assumed equivalent to `UInt { size: 160 }` except for /// interpretation. @@ -58,7 +65,7 @@ pub enum AbiType { }, /// Byte arrays of a fixed `length`, where `0 < length <= 32`. - Bytes { length: u8 }, + Bytes { length: Option }, /// A dynamically-sized array containing elements of a type `tp`. DynArray { @@ -86,7 +93,7 @@ pub enum AbiType { /// /// While the conflict is not usually useful itself, treating them as types /// ensures that we still complete unification as well as is possible. - ConflictedType { left: String, right: String, reason: String }, + ConflictedType { conflicts: Vec, reasons: Vec }, } /// The `U256Wrapper` is responsible for serializing the U256 type to JSON diff --git a/src/inference/expression.rs b/src/inference/expression.rs index c28dbdc..4e5b880 100644 --- a/src/inference/expression.rs +++ b/src/inference/expression.rs @@ -9,7 +9,7 @@ use std::collections::HashSet; use ethnum::U256; use crate::{ - constant::{ADDRESS_WIDTH_BITS, FUNCTION_WIDTH_BITS, SELECTOR_WIDTH_BITS}, + constant::{ADDRESS_WIDTH_BITS, BOOL_WIDTH_BITS, FUNCTION_WIDTH_BITS, SELECTOR_WIDTH_BITS}, inference::state::TypeVariable, }; @@ -37,13 +37,7 @@ pub enum TypeExpression { /// Defaults to [`None`]. width: Option, - /// Whether the word is used as signed. - /// - /// Set to [`None`] if the signedness is unknown. Otherwise set to - /// `true` if signed, and `false` if unsigned. - signed: Option, - - /// Any special way that the word has been used. + /// The way in which the word has been used. usage: WordUse, }, @@ -56,81 +50,72 @@ pub enum TypeExpression { /// A dynamic array containing items with the type of `element`. DynamicArray { element: TypeVariable }, - /// A representation of two conflicting pieces of evidence. - Conflict { - left: Box, - right: Box, - reason: String, - }, + /// A representation of conflicting pieces of evidence. + Conflict { conflicts: Vec>, reasons: Vec }, } impl TypeExpression { - /// Constructs a word with the provided `width` and `signed`. + /// Constructs a word with the provided `width` and `usage`. #[must_use] - pub fn word(width: Option, signed: Option, usage: WordUse) -> Self { - Self::Word { - width, - signed, - usage, - } + pub fn word(width: Option, usage: WordUse) -> Self { + Self::Word { width, usage } } - /// Creates a word with no known `width` or `signed`ness. + /// Creates a word that is numeric with the provided `width`. #[must_use] - pub fn default_word() -> Self { - Self::word(None, None, WordUse::default()) + pub fn numeric(width: Option) -> Self { + Self::word(width, WordUse::Numeric) } /// Constructs an unsigned word with the provided `width`. #[must_use] pub fn unsigned_word(width: Option) -> Self { - let signed = Some(false); - Self::word(width, signed, WordUse::None) + Self::word(width, WordUse::UnsignedNumeric) } /// Constructs a signed word with the provided `width`. #[must_use] pub fn signed_word(width: Option) -> Self { - let signed = Some(true); - Self::word(width, signed, WordUse::None) + Self::word(width, WordUse::SignedNumeric) } /// Constructs a word that has been used as a boolean. #[must_use] pub fn bool() -> Self { - Self::word(None, None, WordUse::Bool) + let usage = WordUse::Bool; + Self::word(usage.size(), usage) } /// Constructs a word that has been used as an address. #[must_use] pub fn address() -> Self { - let width = Some(ADDRESS_WIDTH_BITS); - let signed = Some(false); - Self::word(width, signed, WordUse::Address) + let usage = WordUse::Address; + Self::word(usage.size(), usage) } /// Constructs a word that has been used as a selector. #[must_use] pub fn selector() -> Self { - let width = Some(SELECTOR_WIDTH_BITS); - let signed = Some(false); - Self::word(width, signed, WordUse::Selector) + let usage = WordUse::Selector; + Self::word(usage.size(), usage) } /// Constructs a word that has been used as a function. #[must_use] pub fn function() -> Self { - let width = Some(FUNCTION_WIDTH_BITS); - let signed = Some(false); - Self::word(width, signed, WordUse::Function) + let usage = WordUse::Function; + Self::word(usage.size(), usage) } /// Constructs a word that is actually used as one of the fixed-size byte /// arrays. + /// + /// Note that even though `bytesN` is traditionally denominated in a number + /// of bytes, here the width is still specified in _bits_ as per the unified + /// [`Self::Word`] representation of the type language. #[must_use] pub fn bytes(width: Option) -> Self { - let signed = Some(false); - Self::word(width, signed, WordUse::Bytes) + Self::word(width, WordUse::Bytes) } /// Constructs an equality wrapping the provided type variable `id`. @@ -154,13 +139,29 @@ impl TypeExpression { /// Creates a type expression representing a conflict of the `left` and /// `right` expressions due to `reason`. pub fn conflict(left: Self, right: Self, reason: impl Into) -> Self { - let left = Box::new(left); - let right = Box::new(right); - let reason = reason.into(); + left.conflict_with(right, reason) + } + + /// Conflicts `self` with `other`, combining the conflicts if necessary. + #[must_use] + pub fn conflict_with(self, other: Self, reason: impl Into) -> Self { + let mut all_conflicts = Vec::new(); + let mut all_reasons = vec![reason.into()]; + + let mut gather_expressions = |expr| match expr { + Self::Conflict { conflicts, reasons } => { + all_conflicts.extend(conflicts); + all_reasons.extend(reasons); + } + a => all_conflicts.push(Box::new(a)), + }; + + gather_expressions(self); + gather_expressions(other); + Self::Conflict { - left, - right, - reason, + conflicts: all_conflicts, + reasons: all_reasons, } } @@ -182,10 +183,20 @@ impl TypeExpression { pub type InferenceSet = HashSet; /// A representation of the special ways in which a word could be used. -#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] pub enum WordUse { - /// The word has no special use. - None, + /// The word is used as data (equivalent to `bytesN`) where we know nothing + /// more about it. + Bytes, + + /// The word is used numerically, but is not known to be signed or unsigned. + Numeric, + + /// The word is used numerically and is unsigned. + UnsignedNumeric, + + /// The word is used numerically and is signed. + SignedNumeric, /// The word has been used specifically as a boolean (the result of /// `ISZERO`). @@ -200,68 +211,151 @@ pub enum WordUse { /// The word has been used as a function (an address followed by a /// selector). Function, - - /// The word is used as bytes directly rather than a standard word. - Bytes, - - /// Conflicting usages found. - Conflict { conflicts: Vec }, } impl WordUse { - /// Adds a conflict with `other` for `self`. + /// Converts the usage to the appropriate size in bits if it has an + /// associated size. #[must_use] - pub fn conflict(self, other: Self) -> Self { - match self { - Self::Conflict { mut conflicts } => match other { - Self::Conflict { - conflicts: other_conflicts, - } => { - conflicts.extend(other_conflicts); - Self::Conflict { conflicts } - } - other => { - conflicts.push(other); - Self::Conflict { conflicts } - } - }, - this => match other { - Self::Conflict { mut conflicts } => { - conflicts.push(this); - Self::Conflict { conflicts } - } - other => Self::Conflict { - conflicts: vec![this, other], - }, - }, - } + pub fn size(&self) -> Option { + Some(match self { + Self::Bool => BOOL_WIDTH_BITS, + Self::Address => ADDRESS_WIDTH_BITS, + Self::Selector => SELECTOR_WIDTH_BITS, + Self::Function => FUNCTION_WIDTH_BITS, + _ => return None, + }) } - /// Creates a usage conflict of `left` and `right`. + /// Checks if `self` represents a definitely-signed usage of a word. #[must_use] - pub fn conflict_of(left: Self, other: Self) -> Self { - left.conflict(other) + pub fn is_definitely_signed(&self) -> bool { + matches!(self, Self::SignedNumeric) } - /// Merges two usages if they are compatible, or returns a conflict if not. + /// Merges two usages if they are compatible, or returns [`None`] if they + /// are not. #[must_use] - pub fn merge(self, other: Self) -> Self { + pub fn merge(self, other: Self) -> Option { if self == other { - return self; + return Some(self); } - match (self, other) { - // None can always merge with the other - (Self::None, other) | (other, Self::None) => other, + Some(match (self, other) { + // Data can always be merged with anything that is more specific. + (Self::Bytes, other) | (other, Self::Bytes) => other, - // If they aren't equal, then we have a conflict - (l, r) => l.conflict(r), - } + // Merge the numeric options + (Self::Numeric, Self::UnsignedNumeric) | (Self::UnsignedNumeric, Self::Numeric) => { + Self::UnsignedNumeric + } + (Self::Numeric, Self::SignedNumeric) | (Self::SignedNumeric, Self::Numeric) => { + Self::SignedNumeric + } + + _ => return None, + }) } } impl Default for WordUse { fn default() -> Self { - Self::None + Self::Bytes + } +} + +#[cfg(test)] +mod test { + use crate::{ + constant::{ADDRESS_WIDTH_BITS, BOOL_WIDTH_BITS, FUNCTION_WIDTH_BITS, SELECTOR_WIDTH_BITS}, + inference::expression::WordUse, + }; + + #[test] + fn returns_correct_sizes() { + assert_eq!(WordUse::Bytes.size(), None); + assert_eq!(WordUse::Numeric.size(), None); + assert_eq!(WordUse::UnsignedNumeric.size(), None); + assert_eq!(WordUse::SignedNumeric.size(), None); + assert_eq!(WordUse::Bool.size(), Some(BOOL_WIDTH_BITS)); + assert_eq!(WordUse::Address.size(), Some(ADDRESS_WIDTH_BITS)); + assert_eq!(WordUse::Selector.size(), Some(SELECTOR_WIDTH_BITS)); + assert_eq!(WordUse::Function.size(), Some(FUNCTION_WIDTH_BITS)); + } + + #[test] + fn returns_correct_signedness() { + assert!(!WordUse::Bytes.is_definitely_signed()); + assert!(!WordUse::Numeric.is_definitely_signed()); + assert!(!WordUse::UnsignedNumeric.is_definitely_signed()); + assert!(WordUse::SignedNumeric.is_definitely_signed()); + assert!(!WordUse::Bool.is_definitely_signed()); + assert!(!WordUse::Address.is_definitely_signed()); + assert!(!WordUse::Selector.is_definitely_signed()); + assert!(!WordUse::Function.is_definitely_signed()); + } + + #[test] + fn bytes_overridden_by_all_others_in_merge() { + assert_eq!( + WordUse::Bytes.merge(WordUse::Numeric), + Some(WordUse::Numeric) + ); + assert_eq!( + WordUse::Bytes.merge(WordUse::UnsignedNumeric), + Some(WordUse::UnsignedNumeric) + ); + assert_eq!( + WordUse::Bytes.merge(WordUse::SignedNumeric), + Some(WordUse::SignedNumeric) + ); + assert_eq!(WordUse::Bytes.merge(WordUse::Bool), Some(WordUse::Bool)); + assert_eq!( + WordUse::Bytes.merge(WordUse::Address), + Some(WordUse::Address) + ); + assert_eq!( + WordUse::Bytes.merge(WordUse::Selector), + Some(WordUse::Selector) + ); + assert_eq!( + WordUse::Bytes.merge(WordUse::Function), + Some(WordUse::Function) + ); + } + + #[test] + fn unsigned_numeric_overrides_numeric() { + assert_eq!( + WordUse::Numeric.merge(WordUse::UnsignedNumeric), + Some(WordUse::UnsignedNumeric) + ); + assert_eq!( + WordUse::UnsignedNumeric.merge(WordUse::Numeric), + Some(WordUse::UnsignedNumeric) + ); + } + + #[test] + fn signed_numeric_overrides_numeric() { + assert_eq!( + WordUse::Numeric.merge(WordUse::SignedNumeric), + Some(WordUse::SignedNumeric) + ); + assert_eq!( + WordUse::SignedNumeric.merge(WordUse::Numeric), + Some(WordUse::SignedNumeric) + ); + } + + #[test] + fn signed_numeric_conflicts_with_unsigned_numeric() { + assert!(WordUse::UnsignedNumeric.merge(WordUse::SignedNumeric).is_none()); + assert!(WordUse::SignedNumeric.merge(WordUse::UnsignedNumeric).is_none()); + } + + #[test] + fn returns_none_for_conflict() { + assert!(WordUse::Address.merge(WordUse::Bool).is_none()); } } diff --git a/src/inference/lift/dynamic_array_access.rs b/src/inference/lift/dynamic_array_access.rs index 3cb73bb..633f95e 100644 --- a/src/inference/lift/dynamic_array_access.rs +++ b/src/inference/lift/dynamic_array_access.rs @@ -50,9 +50,17 @@ impl Lift for DynamicArrayAccess { let SVD::Add { left, right } = &key.data else { return None; }; - let SVD::Sha3 { data } = &left.data else { + + // Important to check if either side of the addition is the hash + let data = if let SVD::Sha3 { data } = &left.data { + data + } else if let SVD::Sha3 { data } = &right.data { + data + } else { return None; - }; + } + .clone() + .constant_fold(); let SVD::KnownData { .. } = &data.data else { return None; }; @@ -60,7 +68,7 @@ impl Lift for DynamicArrayAccess { let access = SV::new( key.instruction_pointer, SVD::DynamicArrayAccess { - slot: data.clone().transform_data(lift_dyn_array_accesses), + slot: data.transform_data(lift_dyn_array_accesses), index: right.clone().transform_data(lift_dyn_array_accesses), }, key.provenance, diff --git a/src/inference/lift/mask_word.rs b/src/inference/lift/mask_word.rs deleted file mode 100644 index f12682b..0000000 --- a/src/inference/lift/mask_word.rs +++ /dev/null @@ -1,187 +0,0 @@ -//! This lifting pass looks for operations that mask values to a well-defined -//! length or sub-values, as these can be used to infer the width of a value. - -use crate::{ - inference::{lift::Lift, state::InferenceState}, - vm::value::{BoxedVal, SVD}, -}; - -/// This pass detects and folds expressions that mask word-size values. -/// -/// These have a form as follows: -/// -/// ```code -/// value & ((1 << shift) - 1) -/// -/// becomes -/// -/// mask(value, mask) -/// ``` -/// -/// where: -/// - The operands to `&` may be flipped as the operator is symmetric. -/// - The `((1 << shift) - 1)` mask may be constant-folded. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct MaskWord; - -impl MaskWord { - /// Constructs a new instance of the word mask lifting pass. - #[must_use] - pub fn new() -> Box { - Box::new(Self) - } -} - -impl Lift for MaskWord { - fn run( - &mut self, - value: BoxedVal, - _state: &InferenceState, - ) -> crate::error::unification::Result { - fn mask_arg(data: &SVD) -> Option { - // If there is a known data as the operand, and it hasn't been constant folded, - // we know that the mask itself _has_ been, so we just return it. - if let SVD::KnownData { value } = data { - return Some(usize::from(value)); - } - - let SVD::Subtract { left, right } = data else { - return None; - }; - let SVD::LeftShift { shift, value } = &left.data else { - return None; - }; - let SVD::KnownData { - value: subtracted_value, - } = &right.data - else { - return None; - }; - let SVD::KnownData { - value: shift_amount, - } = &shift.data - else { - return None; - }; - let SVD::KnownData { - value: shifted_value, - } = &value.data - else { - return None; - }; - - if usize::from(subtracted_value) == 1 && usize::from(shifted_value) == 1 { - Some(usize::from(shift_amount)) - } else { - None - } - } - - fn insert_masks(data: &SVD) -> Option { - let SVD::And { left, right } = data else { - return None; - }; - - if let Some(mask_size) = mask_arg(&left.data) { - Some(SVD::WordMask { - value: right.clone().transform_data(insert_masks), - mask: mask_size, - }) - } else { - mask_arg(&right.data).map(|mask_size| SVD::WordMask { - value: left.clone().transform_data(insert_masks), - mask: mask_size, - }) - } - } - - Ok(value.transform_data(insert_masks)) - } -} - -#[cfg(test)] -mod test { - use crate::{ - inference::{ - lift::{mask_word::MaskWord, Lift}, - state::InferenceState, - }, - vm::value::{known::KnownWord, Provenance, SymbolicValue, SymbolicValueData, SVD}, - }; - - #[test] - fn resolves_word_masks_at_top_level() -> anyhow::Result<()> { - let one = SymbolicValue::new_known_value(0, KnownWord::from(1), Provenance::Synthetic); - let shift_amount = - SymbolicValue::new_known_value(1, KnownWord::from(12), Provenance::Synthetic); - let shift = SymbolicValue::new( - 2, - SymbolicValueData::LeftShift { - value: one.clone(), - shift: shift_amount, - }, - Provenance::Synthetic, - ); - let subtract = SymbolicValue::new( - 3, - SymbolicValueData::Subtract { - left: shift, - right: one, - }, - Provenance::Synthetic, - ); - let input_value = SymbolicValue::new_value(4, Provenance::Synthetic); - let and = SymbolicValue::new( - 4, - SymbolicValueData::And { - left: input_value.clone(), - right: subtract, - }, - Provenance::Synthetic, - ); - - let state = InferenceState::empty(); - let result = MaskWord.run(and, &state)?; - - match &result.data { - SVD::WordMask { value, mask } => { - assert_eq!(value, &input_value); - assert_eq!(*mask, 12usize); - } - _ => panic!("Incorrect payload"), - } - - Ok(()) - } - - #[test] - fn resolves_word_masks_with_constant_masks() -> anyhow::Result<()> { - let input_value = SymbolicValue::new_value(4, Provenance::Synthetic); - let input_mask = SymbolicValue::new_known_value( - 0, - KnownWord::from(0x0000_ffff_0000), - Provenance::Synthetic, - ); - let and = SymbolicValue::new( - 4, - SymbolicValueData::And { - left: input_mask, - right: input_value.clone(), - }, - Provenance::Synthetic, - ); - - let state = InferenceState::empty(); - let result = MaskWord.run(and, &state)?; - - match &result.data { - SVD::WordMask { value, mask } => { - assert_eq!(value, &input_value); - assert_eq!(*mask, 0x0000_ffff_0000); - } - _ => panic!("Incorrect payload"), - }; - - Ok(()) - } -} diff --git a/src/inference/lift/mod.rs b/src/inference/lift/mod.rs index cdb7630..a977598 100644 --- a/src/inference/lift/mod.rs +++ b/src/inference/lift/mod.rs @@ -4,9 +4,9 @@ pub mod dynamic_array_access; pub mod mapping_access; -pub mod mask_word; pub mod recognise_hashed_slots; pub mod storage_slots; +pub mod sub_word; use std::{ any::{Any, TypeId}, @@ -21,9 +21,9 @@ use crate::{ lift::{ dynamic_array_access::DynamicArrayAccess, mapping_access::MappingAccess, - mask_word::MaskWord, recognise_hashed_slots::StorageSlotHashes, storage_slots::StorageSlots, + sub_word::SubWordValue, }, state::InferenceState, }, @@ -120,7 +120,7 @@ impl Default for LiftingPasses { Self { passes: vec![ StorageSlotHashes::new(), - MaskWord::new(), + SubWordValue::new(), MappingAccess::new(), DynamicArrayAccess::new(), StorageSlots::new(), diff --git a/src/inference/lift/recognise_hashed_slots.rs b/src/inference/lift/recognise_hashed_slots.rs index b586464..cf16429 100644 --- a/src/inference/lift/recognise_hashed_slots.rs +++ b/src/inference/lift/recognise_hashed_slots.rs @@ -78,7 +78,7 @@ impl Lift for StorageSlotHashes { // Now we can look up the hash we found, and convert it to the `Sha3` of a known // value if it is one. - if let Some(slot_index) = hashes.get_by_left(&known_value.value()) { + if let Some(slot_index) = hashes.get_by_left(&known_value.value_le()) { let data = SV::new_known_value( value_clone.instruction_pointer, KnownWord::from(*slot_index), @@ -130,7 +130,7 @@ mod test { #[test] fn can_recognise_a_slot_hash_in_lifting() -> anyhow::Result<()> { - let word = KnownWord::from(util::expected_hash_from_be_hex_string( + let word = KnownWord::from_le(util::expected_hash_from_be_hex_string( "c2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b", )); let value = SV::new_known_value(0, word, Provenance::Synthetic); diff --git a/src/inference/lift/sub_word.rs b/src/inference/lift/sub_word.rs new file mode 100644 index 0000000..405fdd1 --- /dev/null +++ b/src/inference/lift/sub_word.rs @@ -0,0 +1,390 @@ +//! This lifting pass looks for operations that mask values to create +//! sub-values, as these can be used to infer the width of a value. + +use itertools::Itertools; + +use crate::{ + constant::WORD_SIZE_BITS, + inference::{lift::Lift, state::InferenceState}, + vm::value::{BoxedVal, SVD}, +}; + +/// This pass detects and folds expressions that mask word-size values where the +/// masks are recursively constant. +/// +/// These have a form as follows: +/// +/// ```code +/// value & mask +/// +/// becomes +/// +/// sub_word(value, offset, length) +/// ``` +/// +/// where: +/// - The operands to `&` may be flipped as the operator is symmetric. +/// - `mask` is some recursively constant value (e.g. `((1 << shift) - 1)` where +/// `shift` is recursively constant). Recursively constant means that the +/// expression is either encoded as a constant in the bytecode, or composed of +/// operations that can be constant folded on constants in the bytecode). In +/// essence, that the expression can be _turned into_ a constant. +/// - `offset` is computed from the value of the `mask`, and is the 0-based +/// offset in bits from the start of the word. +/// - `length` is the size of the sub-word value, also computed from the `mask` +/// +/// It works on the assumption that the solidity compiler only ever generates +/// masks that are recursively constant. This seems to bear out in practice, and +/// means that if the mask cannot be resolved to a constant we do not have a +/// valid masking operation. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct SubWordValue; + +impl SubWordValue { + /// Constructs a new instance of the sub-word value lifting pass. + #[must_use] + pub fn new() -> Box { + Box::new(Self) + } + + /// Gets the `offset` and `length` of the sub-word if it exists, returning + /// [`None`] otherwise. + /// + /// This function assumes that `data` is the potential mask to inspect. + #[must_use] + pub fn get_region(data: &SVD) -> Option { + // The word to pull the mask details out of + let SVD::KnownData { value } = data.clone().constant_fold() else { + // If it does not fold to a constant we cannot actually work out the details of + // the mask here, so we fail out + return None; + }; + + // To work out the details of the mask we need to inspect the bit pattern; this + // can be done using bit ops, but using a bitvec is clearer and easier to + // understand + let word_bits = value.bits_le(); + + // Finding the first 1 bit tells us where the mask starts + let Some((offset, _)) = word_bits.iter().find_position(|bit| bit == &true) else { + // If we never see a 1 bit the mask is all zeroes + return None; + }; + + // Finding the first 0 bit after the offset tells us the size of the mask, but + // if we don't find a zero bit afterward, the mask is the rest of the word + let size = word_bits + .iter() + .skip(offset) + .find_position(|bit| bit == &false) + .map_or(WORD_SIZE_BITS - offset, |(offset, _)| offset); + + Some(SubWord::new(offset, size)) + } +} + +impl Lift for SubWordValue { + fn run( + &mut self, + value: BoxedVal, + _state: &InferenceState, + ) -> crate::error::unification::Result { + /// Inserts any sub word accesses that it can find. + fn insert_sub_words(data: &SVD) -> Option { + // At the top level it must be an 'and' + let SVD::And { left, right } = data else { + return None; + }; + + // These can be ordered either way around the `and`, so we have to check both + // sides + let (value, SubWord { offset, length }) = + if let Some(word) = SubWordValue::get_region(&left.data) { + (right.clone().transform_data(insert_sub_words), word) + } else if let Some(word) = SubWordValue::get_region(&right.data) { + (left.clone().transform_data(insert_sub_words), word) + } else { + return None; + }; + + // If we find a word, we can easily construct the return data + let payload = SVD::SubWord { + value, + offset, + size: length, + }; + Some(payload) + } + + Ok(value.transform_data(insert_sub_words)) + } +} + +/// A representation of a region within an EVM word that is treated as a single +/// value. +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] +pub struct SubWord { + /// The zero-indexed offset in bits from the start (LSB) of the word where + /// the sub-word begins. + pub offset: usize, + + /// The length in bits of the sub-word. + pub length: usize, +} + +impl SubWord { + /// Creates a new `SubWord` with the specified `offset` and `length`. + #[must_use] + pub fn new(offset: usize, length: usize) -> Self { + Self { offset, length } + } +} + +#[cfg(test)] +mod test { + use crate::{ + inference::{ + lift::{sub_word::SubWordValue, Lift}, + state::InferenceState, + }, + vm::value::{known::KnownWord, Provenance, SV, SVD}, + }; + + #[test] + fn computes_correct_mask_with_no_offset() { + // A mask that is the lowest 64 bits of the word + let mask = KnownWord::from_le(0xffff_ffff_ffff_ffffu128); + let data = SVD::new_known(mask); + + // Run the decomposition process on the mask + let result = SubWordValue::get_region(&data).expect("Mask resolution failed"); + + // Check that it is correct + assert_eq!(result.offset, 0); + assert_eq!(result.length, 64); + } + + #[test] + fn computes_correct_mask_with_offset() { + // A mask that is lowest 32 bits of skip then 64 bits of the data + let mask = KnownWord::from_le_bytes([ + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ]); + let data = SVD::new_known(mask); + + // Run the decomposition process on the mask + let result = SubWordValue::get_region(&data).expect("Mask resolution failed"); + + // Check that it is correct + assert_eq!(result.offset, 32); + assert_eq!(result.length, 64); + } + + #[test] + fn computes_correct_mask_as_rest_of_word() { + // A mask that is the lowest 128 bits of the word + let mask = KnownWord::from_le_bytes([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + ]); + let data = SVD::new_known(mask); + + // Run the decomposition process on the mask + let result = SubWordValue::get_region(&data).expect("Mask resolution failed"); + + // Check that it is correct + assert_eq!(result.offset, 128); + assert_eq!(result.length, 128); + } + + #[test] + fn computes_correct_mask_from_expression() { + // A mask that is the lowest 192 bits of the word + let shift_amount = + SV::new_known_value(0, KnownWord::from_le(0xc0u32), Provenance::Synthetic); + let one = SV::new_known_value(1, KnownWord::from_le(1u32), Provenance::Synthetic); + let shift = SV::new_synthetic( + 2, + SVD::LeftShift { + shift: shift_amount, + value: one.clone(), + }, + ); + let subtract = SV::new_synthetic( + 3, + SVD::Subtract { + left: shift, + right: one, + }, + ); + + // Run the decomposition process on the mask + let result = SubWordValue::get_region(&subtract.data).expect("Mask resolution failed"); + + // Check that it is correct + assert_eq!(result.offset, 0); + assert_eq!(result.length, 192); + } + + #[test] + fn does_not_compute_mask_for_non_constant_fold() { + // A mask that we cannot do a single thing with + let mask = SV::new_value(0, Provenance::Synthetic); + + // Run the decomposition process on the mask + let result = SubWordValue::get_region(&mask.data); + + // Check it fails out + assert!(result.is_none()); + } + + #[test] + fn resolves_word_masks() -> anyhow::Result<()> { + // Construct the mask value itself (lowest 192 bits of the word) + let shift_amount = + SV::new_known_value(0, KnownWord::from_le(0xc0u32), Provenance::Synthetic); + let one = SV::new_known_value(1, KnownWord::from_le(1u32), Provenance::Synthetic); + let shift = SV::new_synthetic( + 2, + SVD::LeftShift { + shift: shift_amount, + value: one.clone(), + }, + ); + let mask = SV::new_synthetic( + 3, + SVD::Subtract { + left: shift, + right: one, + }, + ); + + // Create the value being masked + let input_value = SV::new_value(4, Provenance::Synthetic); + + // Create the two ways round we want to test + let mask_on_left = SV::new_synthetic( + 5, + SVD::And { + left: mask.clone(), + right: input_value.clone(), + }, + ); + let mask_on_right = SV::new_synthetic( + 5, + SVD::And { + left: input_value.clone(), + right: mask, + }, + ); + + // Run the lifting pass on both + let state = InferenceState::empty(); + let result_on_left = SubWordValue.run(mask_on_left, &state)?; + let result_on_right = SubWordValue.run(mask_on_right, &state)?; + + // Check that it is correct + assert_eq!(result_on_left, result_on_right); + + match result_on_left.data { + SVD::SubWord { + value, + offset, + size, + } => { + assert_eq!(value, input_value); + assert_eq!(offset, 0); + assert_eq!(size, 192); + } + _ => panic!("Incorrect payload"), + } + + Ok(()) + } + + #[test] + fn resolves_word_masks_recursively() -> anyhow::Result<()> { + // Construct the mask value itself (lowest 192 bits of the word) + let shift_amount = + SV::new_known_value(0, KnownWord::from_le(0xc0u32), Provenance::Synthetic); + let one = SV::new_known_value(1, KnownWord::from_le(1u32), Provenance::Synthetic); + let shift = SV::new_synthetic( + 2, + SVD::LeftShift { + shift: shift_amount, + value: one.clone(), + }, + ); + let mask = SV::new_synthetic( + 3, + SVD::Subtract { + left: shift, + right: one, + }, + ); + + // Create the value being masked and the mask operation + let input_value = SV::new_value(4, Provenance::Synthetic); + let mask_operation = SV::new_synthetic( + 5, + SVD::And { + left: mask.clone(), + right: input_value.clone(), + }, + ); + + // Now wrap these in another mask to check recursion + let recurse_on_left = SV::new_synthetic( + 6, + SVD::And { + left: mask_operation.clone(), + right: mask.clone(), + }, + ); + let recurse_on_right = SV::new_synthetic( + 7, + SVD::And { + left: mask, + right: mask_operation, + }, + ); + + // Run the lifting pass on both + let state = InferenceState::empty(); + let result_on_left = SubWordValue.run(recurse_on_left, &state)?; + let result_on_right = SubWordValue.run(recurse_on_right, &state)?; + + // Check that it is correct + assert_eq!(result_on_left, result_on_right); + + match result_on_left.data { + SVD::SubWord { + value, + offset, + size, + } => { + assert_eq!(offset, 0); + assert_eq!(size, 192); + + match value.data { + SVD::SubWord { + value, + offset, + size, + } => { + assert_eq!(offset, 0); + assert_eq!(size, 192); + assert_eq!(value, input_value); + } + _ => panic!("Incorrect payload"), + } + } + _ => panic!("Incorrect payload"), + } + + Ok(()) + } +} diff --git a/src/inference/mod.rs b/src/inference/mod.rs index bbb6c98..1fd6e51 100644 --- a/src/inference/mod.rs +++ b/src/inference/mod.rs @@ -5,7 +5,7 @@ use std::collections::{HashSet, VecDeque}; use self::abi::U256Wrapper; use crate::{ - constant::WORD_SIZE, + constant::BYTE_SIZE, error::{ container::Locatable, unification::{Error, Errors, Result}, @@ -198,9 +198,11 @@ impl InferenceEngine { }; let index: usize = value.into(); - let (abi_type, defaulted) = self.abi_type_for(ty_var)?; + let abi_type = self.abi_type_for(ty_var)?; - layout.add(index, abi_type, defaulted); + // For now we add things with a constant offset of zero as we don't deal with + // sub-word access + layout.add(index, 0, abi_type); } Ok(layout) @@ -215,7 +217,7 @@ impl InferenceEngine { /// # Errors /// /// If something goes wrong in the computation of the [`AbiType`]. - fn abi_type_for(&mut self, var: TypeVariable) -> Result<(AbiType, bool)> { + fn abi_type_for(&mut self, var: TypeVariable) -> Result { let mut seen_vars = HashSet::new(); self.abi_type_for_impl(var, &mut seen_vars) } @@ -231,102 +233,93 @@ impl InferenceEngine { &mut self, var: TypeVariable, seen_exprs: &mut HashSet, - ) -> Result<(AbiType, bool)> { + ) -> Result { let type_expr = self.type_of(var)?; // If we see the same type constructor again when iterating, the type is // infinite so we short-circuit if seen_exprs.contains(&type_expr) && type_expr.is_type_constructor() { - return Ok((AbiType::InfiniteType, false)); + return Ok(AbiType::InfiniteType); } + seen_exprs.insert(type_expr.clone()); - let mut defaulted = false; + // Get the location in case an error needs to be raised + let location = self.state.value(var).unwrap().instruction_pointer; let abi_type: AbiType = match type_expr { TE::Any => AbiType::Any, - TE::Word { - width, - signed, - usage, - } => { - let signed = signed.unwrap_or_else(|| { - defaulted = true; - false - }); - - // And we default to the EVM's word size otherwise - let width = width.unwrap_or_else(|| { - defaulted = true; - WORD_SIZE - }); - - let size = if let Ok(v) = u16::try_from(width) { - v - } else { - let location = self.state.value(var).unwrap().instruction_pointer; - Err(Error::OverSizedNumber { - value: width as i128, - width: 16, + TE::Word { width, usage } => match usage { + WordUse::Bytes => AbiType::Bytes { + length: width.map(|w| w / BYTE_SIZE), + }, + WordUse::Numeric => AbiType::Number { size: width }, + WordUse::UnsignedNumeric => AbiType::UInt { size: width }, + WordUse::SignedNumeric => AbiType::Int { size: width }, + WordUse::Bool => { + if width != usage.size() { + return Err(Error::InvalidInference { + value: type_expr, + reason: "Bool inferred with incorrect width".into(), + } + .locate(location) + .into()); } - .locate(location))? - }; - - let from_signed = |signed| { - if signed { - AbiType::Int { size } - } else { - AbiType::UInt { size } + AbiType::Bool + } + WordUse::Address => { + if width != usage.size() { + return Err(Error::InvalidInference { + value: type_expr, + reason: "Address inferred with incorrect width".into(), + } + .locate(location) + .into()); } - }; - - match usage { - WordUse::Bool => AbiType::Bool, - WordUse::Address => AbiType::Address, - WordUse::Selector => AbiType::Selector, - WordUse::Function => AbiType::Function, - WordUse::None => from_signed(signed), - WordUse::Bytes => { - if let Ok(length) = u8::try_from(width / 32) { - AbiType::Bytes { length } - } else { - let location = self.state.value(var).unwrap().instruction_pointer; - Err(Error::OverSizedNumber { - value: (width / 32) as i128, - width: 8, - }) - .locate(location)? + AbiType::Address + } + WordUse::Selector => { + if width != usage.size() { + return Err(Error::InvalidInference { + value: type_expr, + reason: "Selector inferred with incorrect width".into(), } + .locate(location) + .into()); } - WordUse::Conflict { .. } => { - defaulted = true; - from_signed(signed) + AbiType::Selector + } + WordUse::Function => { + if width != usage.size() { + return Err(Error::InvalidInference { + value: type_expr, + reason: "Function inferred with incorrect width".into(), + } + .locate(location) + .into()); } + AbiType::Function } - } + }, TE::FixedArray { element, length } => { - let (tp, inner_defaulted) = self.abi_type_for_impl(element, seen_exprs)?; - defaulted = inner_defaulted; + let tp = self.abi_type_for_impl(element, seen_exprs)?; AbiType::Array { size: U256Wrapper(length), tp: Box::new(tp), } } TE::Mapping { key, value } => { - let (key_tp, key_defaulted) = self.abi_type_for_impl(key, seen_exprs)?; - let (val_tp, val_defaulted) = self.abi_type_for_impl(value, seen_exprs)?; - defaulted = key_defaulted || val_defaulted; + let key_tp = self.abi_type_for_impl(key, seen_exprs)?; + let val_tp = self.abi_type_for_impl(value, seen_exprs)?; AbiType::Mapping { key_tp: Box::new(key_tp), val_tp: Box::new(val_tp), } } TE::DynamicArray { element } => { - let (tp, inner_defaulted) = self.abi_type_for_impl(element, seen_exprs)?; - defaulted = inner_defaulted; + let tp = self.abi_type_for_impl(element, seen_exprs)?; AbiType::DynArray { tp: Box::new(tp) } } TE::Equal { id } => { - let location = self.state.value(var).unwrap().instruction_pointer; return Err(Error::InvalidInference { value: TE::Equal { id }, reason: "Equalities cannot be converted into ABI types".into(), @@ -334,18 +327,13 @@ impl InferenceEngine { .locate(location) .into()); } - TE::Conflict { - left, - right, - reason, - } => AbiType::ConflictedType { - left: format!("{left:?}"), - right: format!("{right:?}"), - reason, + TE::Conflict { conflicts, reasons } => AbiType::ConflictedType { + reasons, + conflicts: conflicts.into_iter().map(|c| format!("{c:?}")).collect(), }, }; - Ok((abi_type, defaulted)) + Ok(abi_type) } /// Gets the unified type for the provided `type_variable`. @@ -606,7 +594,7 @@ pub mod test { first_slot.typ, AbiType::Mapping { key_tp: Box::new(AbiType::Any), - val_tp: Box::new(AbiType::UInt { size: 256 }), + val_tp: Box::new(AbiType::Number { size: None }), } ); diff --git a/src/inference/rule/arithmetic_operations.rs b/src/inference/rule/arithmetic_operations.rs index 67fe360..94cefc3 100644 --- a/src/inference/rule/arithmetic_operations.rs +++ b/src/inference/rule/arithmetic_operations.rs @@ -2,7 +2,7 @@ //! operations. use crate::{ - constant::WORD_SIZE, + constant::WORD_SIZE_BITS, error::unification::Result, inference::{expression::TE, rule::InferenceRule, state::InferenceState}, vm::value::{BoxedVal, SVD}, @@ -19,10 +19,10 @@ impl InferenceRule for ArithmeticOperationRule { SVD::Add { left, right } | SVD::Multiply { left, right } | SVD::Subtract { left, right } => { - state.infer_for_many([value, left, right], TE::default_word()); + state.infer_for_many([value, left, right], TE::numeric(None)); } SVD::Divide { dividend, divisor } | SVD::Modulo { dividend, divisor } => { - state.infer_for_many([value, dividend, divisor], TE::default_word()); + state.infer_for_many([value, dividend, divisor], TE::unsigned_word(None)); } SVD::SignedDivide { dividend, divisor } | SVD::SignedModulo { dividend, divisor } => { state.infer_for_many([value, dividend, divisor], TE::signed_word(None)); @@ -31,7 +31,7 @@ impl InferenceRule for ArithmeticOperationRule { value: exp_val, exponent, } => { - state.infer_for_many([value, exp_val, exponent], TE::default_word()); + state.infer_for_many([value, exp_val, exponent], TE::numeric(None)); } SVD::SignExtend { value: extend_val, @@ -44,7 +44,7 @@ impl InferenceRule for ArithmeticOperationRule { let width = if let SVD::KnownData { value } = &size.data { let width: usize = value.into(); - if width <= WORD_SIZE { + if width <= WORD_SIZE_BITS { Some(width) } else { None @@ -93,9 +93,9 @@ mod test { ArithmeticOperationRule.infer(&add, &mut state)?; // Check we get the right equations - assert!(state.inferences(left_ty).contains(&TE::default_word())); - assert!(state.inferences(right_ty).contains(&TE::default_word())); - assert!(state.inferences(add_ty).contains(&TE::default_word())); + assert!(state.inferences(left_ty).contains(&TE::numeric(None))); + assert!(state.inferences(right_ty).contains(&TE::numeric(None))); + assert!(state.inferences(add_ty).contains(&TE::numeric(None))); Ok(()) } @@ -120,9 +120,9 @@ mod test { ArithmeticOperationRule.infer(&mul, &mut state)?; // Check we get the right equations - assert!(state.inferences(left_ty).contains(&TE::default_word())); - assert!(state.inferences(right_ty).contains(&TE::default_word())); - assert!(state.inferences(mul_ty).contains(&TE::default_word())); + assert!(state.inferences(left_ty).contains(&TE::numeric(None))); + assert!(state.inferences(right_ty).contains(&TE::numeric(None))); + assert!(state.inferences(mul_ty).contains(&TE::numeric(None))); Ok(()) } @@ -147,9 +147,9 @@ mod test { ArithmeticOperationRule.infer(&sub, &mut state)?; // Check we get the right equations - assert!(state.inferences(left_ty).contains(&TE::default_word())); - assert!(state.inferences(right_ty).contains(&TE::default_word())); - assert!(state.inferences(sub_ty).contains(&TE::default_word())); + assert!(state.inferences(left_ty).contains(&TE::numeric(None))); + assert!(state.inferences(right_ty).contains(&TE::numeric(None))); + assert!(state.inferences(sub_ty).contains(&TE::numeric(None))); Ok(()) } @@ -175,9 +175,9 @@ mod test { ArithmeticOperationRule.infer(&div, &mut state)?; // Check we get the right equations - assert!(state.inferences(dividend_ty).contains(&TE::default_word())); - assert!(state.inferences(divisor_ty).contains(&TE::default_word())); - assert!(state.inferences(div_ty).contains(&TE::default_word())); + assert!(state.inferences(dividend_ty).contains(&TE::unsigned_word(None))); + assert!(state.inferences(divisor_ty).contains(&TE::unsigned_word(None))); + assert!(state.inferences(div_ty).contains(&TE::unsigned_word(None))); Ok(()) } @@ -231,9 +231,9 @@ mod test { ArithmeticOperationRule.infer(&modulo, &mut state)?; // Check we get the right equations - assert!(state.inferences(dividend_ty).contains(&TE::default_word())); - assert!(state.inferences(divisor_ty).contains(&TE::default_word())); - assert!(state.inferences(div_ty).contains(&TE::default_word())); + assert!(state.inferences(dividend_ty).contains(&TE::unsigned_word(None))); + assert!(state.inferences(divisor_ty).contains(&TE::unsigned_word(None))); + assert!(state.inferences(div_ty).contains(&TE::unsigned_word(None))); Ok(()) } @@ -286,9 +286,9 @@ mod test { ArithmeticOperationRule.infer(&exp, &mut state)?; // Check we get the right equations - assert!(state.inferences(value_ty).contains(&TE::default_word())); - assert!(state.inferences(exponent_ty).contains(&TE::default_word())); - assert!(state.inferences(exp_ty).contains(&TE::default_word())); + assert!(state.inferences(value_ty).contains(&TE::numeric(None))); + assert!(state.inferences(exponent_ty).contains(&TE::numeric(None))); + assert!(state.inferences(exp_ty).contains(&TE::numeric(None))); Ok(()) } diff --git a/src/inference/rule/bit_shifts.rs b/src/inference/rule/bit_shifts.rs index 0096bc7..f574a77 100644 --- a/src/inference/rule/bit_shifts.rs +++ b/src/inference/rule/bit_shifts.rs @@ -30,8 +30,8 @@ impl InferenceRule for BitShiftRule { // The shift amount is always interpreted as unsigned state.infer_for(shift, TE::unsigned_word(None)); - // We know nothing about the result of a normal bit shift or the value - state.infer_for_many([value, shift_val], TE::default_word()); + // We know little about the result of a normal bit shift or the value + state.infer_for_many([value, shift_val], TE::bytes(None)); } SVD::ArithmeticRightShift { shift, @@ -82,9 +82,9 @@ mod test { BitShiftRule.infer(&shift, &mut state)?; // Check we get the expected equations - assert!(state.inferences(value_tv).contains(&TE::default_word())); + assert!(state.inferences(value_tv).contains(&TE::bytes(None))); assert!(state.inferences(shift_amount_tv).contains(&TE::unsigned_word(None))); - assert!(state.inferences(shift_tv).contains(&TE::default_word())); + assert!(state.inferences(shift_tv).contains(&TE::bytes(None))); Ok(()) } @@ -109,9 +109,9 @@ mod test { BitShiftRule.infer(&shift, &mut state)?; // Check we get the expected equations - assert!(state.inferences(value_tv).contains(&TE::default_word())); + assert!(state.inferences(value_tv).contains(&TE::bytes(None))); assert!(state.inferences(shift_amount_tv).contains(&TE::unsigned_word(None))); - assert!(state.inferences(shift_tv).contains(&TE::default_word())); + assert!(state.inferences(shift_tv).contains(&TE::bytes(None))); Ok(()) } diff --git a/src/inference/rule/boolean_operations.rs b/src/inference/rule/boolean_operations.rs index 9911be5..6da0fc9 100644 --- a/src/inference/rule/boolean_operations.rs +++ b/src/inference/rule/boolean_operations.rs @@ -34,21 +34,20 @@ impl InferenceRule for BooleanOpsRule { } // Equality can operate over arbitrary words, of any width SVD::Equals { left, right } => { - state.infer_for_many([left, right], TE::default_word()); + state.infer_for_many([left, right], TE::bytes(None)); state.infer_for(value, TE::bool()); } // This is a numeric comparison to zero SVD::IsZero { number } => { - state.infer_for(number, TE::default_word()); + state.infer_for(number, TE::numeric(None)); state.infer_for(value, TE::bool()); } // These operate over arbitrary words as well SVD::And { left, right } | SVD::Or { left, right } | SVD::Xor { left, right } => { - state.infer_for_many([left, right], TE::default_word()); - state.infer_for(value, TE::default_word()); + state.infer_for_many([left, right, value], TE::bytes(None)); } SVD::Not { value: not_val } => { - state.infer_for_many([value, not_val], TE::default_word()); + state.infer_for_many([value, not_val], TE::bytes(None)); } _ => (), } @@ -191,8 +190,8 @@ mod test { BooleanOpsRule.infer(&operator, &mut state)?; // Check we get the right equations - assert!(state.inferences(left_tv).contains(&TE::default_word())); - assert!(state.inferences(right_tv).contains(&TE::default_word())); + assert!(state.inferences(left_tv).contains(&TE::bytes(None))); + assert!(state.inferences(right_tv).contains(&TE::bytes(None))); assert!(state.inferences(operator_tv).contains(&TE::bool())); Ok(()) @@ -215,7 +214,7 @@ mod test { BooleanOpsRule.infer(&operator, &mut state)?; // Check we get the right equations - assert!(state.inferences(value_tv).contains(&TE::default_word())); + assert!(state.inferences(value_tv).contains(&TE::numeric(None))); assert!(state.inferences(operator_tv).contains(&TE::bool())); Ok(()) @@ -240,9 +239,9 @@ mod test { BooleanOpsRule.infer(&operator, &mut state)?; // Check we get the right equations - assert!(state.inferences(left_tv).contains(&TE::default_word())); - assert!(state.inferences(right_tv).contains(&TE::default_word())); - assert!(state.inferences(operator_tv).contains(&TE::default_word())); + assert!(state.inferences(left_tv).contains(&TE::bytes(None))); + assert!(state.inferences(right_tv).contains(&TE::bytes(None))); + assert!(state.inferences(operator_tv).contains(&TE::bytes(None))); Ok(()) } @@ -266,9 +265,9 @@ mod test { BooleanOpsRule.infer(&operator, &mut state)?; // Check we get the right equations - assert!(state.inferences(left_tv).contains(&TE::default_word())); - assert!(state.inferences(right_tv).contains(&TE::default_word())); - assert!(state.inferences(operator_tv).contains(&TE::default_word())); + assert!(state.inferences(left_tv).contains(&TE::bytes(None))); + assert!(state.inferences(right_tv).contains(&TE::bytes(None))); + assert!(state.inferences(operator_tv).contains(&TE::bytes(None))); Ok(()) } @@ -292,9 +291,9 @@ mod test { BooleanOpsRule.infer(&operator, &mut state)?; // Check we get the right equations - assert!(state.inferences(left_tv).contains(&TE::default_word())); - assert!(state.inferences(right_tv).contains(&TE::default_word())); - assert!(state.inferences(operator_tv).contains(&TE::default_word())); + assert!(state.inferences(left_tv).contains(&TE::bytes(None))); + assert!(state.inferences(right_tv).contains(&TE::bytes(None))); + assert!(state.inferences(operator_tv).contains(&TE::bytes(None))); Ok(()) } @@ -316,8 +315,8 @@ mod test { BooleanOpsRule.infer(&operator, &mut state)?; // Check we get the right equations - assert!(state.inferences(value_tv).contains(&TE::default_word())); - assert!(state.inferences(operator_tv).contains(&TE::default_word())); + assert!(state.inferences(value_tv).contains(&TE::bytes(None))); + assert!(state.inferences(operator_tv).contains(&TE::bytes(None))); Ok(()) } diff --git a/src/inference/rule/call_data.rs b/src/inference/rule/call_data.rs new file mode 100644 index 0000000..33ebc96 --- /dev/null +++ b/src/inference/rule/call_data.rs @@ -0,0 +1,152 @@ +//! This module contains an inference rule that says that a value has a size +//! equivalent to the size that created it when reading from call data. + +use crate::{ + constant::BYTE_SIZE, + inference::{expression::TE, rule::InferenceRule, state::InferenceState}, + vm::value::{known::KnownWord, BoxedVal, SVD}, +}; + +/// This rule creates equations as described below for expressions of the +/// following form. +/// +/// ```text +/// call_data[id](offset, read_size) +/// a b c +/// ``` +/// +/// equates: +/// - `a = word(size = read_size, usage = Bytes)` if `read_size` is constant +/// +/// Note that `call_data` is created from both `CallDataLoad`, which loads a +/// single word, and `CallDataCopy`, which may load much more than a single +/// word. In the case where the read size during `CallDataCopy` is constant +/// however, we instead create individual words in memory with defined sizes. +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] +pub struct CallDataRule; + +impl InferenceRule for CallDataRule { + fn infer( + &self, + value: &BoxedVal, + state: &mut InferenceState, + ) -> crate::error::unification::Result<()> { + let SVD::CallData { size, .. } = &value.data else { + return Ok(()); + }; + + // If we can make the size into a constant we can work with it + let SVD::KnownData { value: byte_size } = size.data.clone().constant_fold() else { + return Ok(()); + }; + + // Otherwise, we can infer that the type of the value is word + let value_bits: usize = >::into(byte_size) * BYTE_SIZE; + state.infer_for(value, TE::bytes(Some(value_bits))); + + // All done + Ok(()) + } +} + +#[cfg(test)] +mod test { + use crate::{ + constant::BYTE_SIZE, + inference::{ + expression::TE, + rule::{call_data::CallDataRule, InferenceRule}, + state::InferenceState, + }, + vm::value::{known::KnownWord, Provenance, SV, SVD}, + }; + + #[test] + fn writes_no_inferences_for_non_constant_sizes() -> anyhow::Result<()> { + // Create the values to run inference on + let offset = SV::new_value(0, Provenance::Synthetic); + let size = SV::new_value(1, Provenance::Synthetic); + let call_data = SV::new_synthetic(2, SVD::call_data(offset.clone(), size.clone())); + + // Register them in the state + let mut state = InferenceState::empty(); + let [offset_tv, size_tv, call_data_tv] = + state.register_many([offset, size, call_data.clone()]); + + // Run the inference rule + CallDataRule.infer(&call_data, &mut state)?; + + // Check that we end up with no equations + assert!(state.inferences(offset_tv).is_empty()); + assert!(state.inferences(size_tv).is_empty()); + assert!(state.inferences(call_data_tv).is_empty()); + + Ok(()) + } + + #[test] + fn writes_inferences_for_constant_sizes() -> anyhow::Result<()> { + // Create the values to run inference on + let offset = SV::new_value(0, Provenance::Synthetic); + let size = SV::new_known_value(1, KnownWord::from_le(16u32), Provenance::Synthetic); + let call_data = SV::new_synthetic(2, SVD::call_data(offset.clone(), size.clone())); + + // Register them in the state + let mut state = InferenceState::empty(); + let [offset_tv, size_tv, call_data_tv] = + state.register_many([offset, size, call_data.clone()]); + + // Run the inference rule + CallDataRule.infer(&call_data, &mut state)?; + + // Check that we end up with no equations + assert!(state.inferences(offset_tv).is_empty()); + assert!(state.inferences(size_tv).is_empty()); + assert_eq!(state.inferences(call_data_tv).len(), 1); + assert!( + state + .inferences(call_data_tv) + .contains(&TE::bytes(Some(16 * BYTE_SIZE))) + ); + + Ok(()) + } + + #[test] + fn writes_inferences_when_size_needs_folding() -> anyhow::Result<()> { + // Create the values to run inference on + let offset = SV::new_value(0, Provenance::Synthetic); + let left_val = SV::new_known_value(1, KnownWord::from_le(7u32), Provenance::Synthetic); + let right_val = SV::new_known_value(2, KnownWord::from_le(9u32), Provenance::Synthetic); + let size = SV::new_synthetic( + 3, + SVD::Add { + left: left_val.clone(), + right: right_val.clone(), + }, + ); + let call_data = SV::new_synthetic(4, SVD::call_data(offset.clone(), size.clone())); + + // Register them in the state + let mut state = InferenceState::empty(); + let [offset_tv, left_val_tv, right_val_tv, size_tv, call_data_tv] = + state.register_many([offset, left_val, right_val, size, call_data.clone()]); + + // Run the inference rule + CallDataRule.infer(&call_data, &mut state)?; + + // Check that we end up with no equations + assert!(state.inferences(offset_tv).is_empty()); + assert!(state.inferences(left_val_tv).is_empty()); + assert!(state.inferences(right_val_tv).is_empty()); + assert!(state.inferences(size_tv).is_empty()); + assert_eq!(state.inferences(call_data_tv).len(), 1); + assert!( + state + .inferences(call_data_tv) + .contains(&TE::bytes(Some(16 * BYTE_SIZE))) + ); + + Ok(()) + } +} diff --git a/src/inference/rule/create.rs b/src/inference/rule/create.rs index aeee445..991e158 100644 --- a/src/inference/rule/create.rs +++ b/src/inference/rule/create.rs @@ -2,7 +2,7 @@ //! return values when creating new contracts. use crate::{ - constant::WORD_SIZE, + constant::WORD_SIZE_BITS, inference::{expression::TE, rule::InferenceRule, state::InferenceState}, vm::value::{BoxedVal, SVD}, }; @@ -35,7 +35,7 @@ impl InferenceRule for CreateContractRule { } => { state.infer_for(value, TE::address()); state.infer_for(create_val, TE::unsigned_word(None)); - state.infer_for(salt, TE::bytes(Some(WORD_SIZE))); + state.infer_for(salt, TE::bytes(Some(WORD_SIZE_BITS))); } _ => (), } @@ -47,7 +47,7 @@ impl InferenceRule for CreateContractRule { #[cfg(test)] mod test { use crate::{ - constant::WORD_SIZE, + constant::WORD_SIZE_BITS, inference::{ expression::TE, rule::{create::CreateContractRule, InferenceRule}, @@ -105,7 +105,7 @@ mod test { // Check that the equations are right assert!(state.inferences(value_tv).contains(&TE::unsigned_word(None))); - assert!(state.inferences(salt_tv).contains(&TE::bytes(Some(WORD_SIZE)))); + assert!(state.inferences(salt_tv).contains(&TE::bytes(Some(WORD_SIZE_BITS)))); assert!(state.inferences(data_tv).is_empty()); assert!(state.inferences(create_tv).contains(&TE::address())); diff --git a/src/inference/rule/dynamic_array_write.rs b/src/inference/rule/dynamic_array_write.rs index cb80fa7..c11707f 100644 --- a/src/inference/rule/dynamic_array_write.rs +++ b/src/inference/rule/dynamic_array_write.rs @@ -7,18 +7,18 @@ use crate::{ vm::value::{BoxedVal, SVD}, }; -/// This rule creates the equations `d = dynamic_array`, `f = unsigned`, `b = -/// g` for expressions of the following form. +/// This rule creates the following equations in the typing state for +/// expressions of the following form. /// /// ```code /// s_store(storage_slot(dynamic_array[index]), value) /// a b c d e f g /// ``` /// -/// where -/// - The second row are the type variable names for the corresponding -/// expression above. These type variables extend over the whole enclosing -/// expression. +/// equating +/// - `d = dynamic_array` +/// - `f = word(width = unknown, usage = UnsignedWord)` +/// - `b = g` #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] pub struct DynamicArrayWriteRule; diff --git a/src/inference/rule/masked_word.rs b/src/inference/rule/masked_word.rs new file mode 100644 index 0000000..5c20e3d --- /dev/null +++ b/src/inference/rule/masked_word.rs @@ -0,0 +1,81 @@ +//! This module contains an inference rule that deals with inferring type widths +//! from value sub-word masking operations. + +use crate::{ + inference::{expression::TE, rule::InferenceRule, state::InferenceState}, + vm::value::{BoxedVal, SVD}, +}; + +/// This rule creates equations as follows for operations that mask values to +/// sub-word values. +/// +/// ```text +/// sub_word(value, offset, size) +/// a b +/// ``` +/// +/// equating: +/// - `a = word(width = size, usage = Bytes)` +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] +pub struct MaskedWordRule; + +impl InferenceRule for MaskedWordRule { + fn infer( + &self, + value: &BoxedVal, + state: &mut InferenceState, + ) -> crate::error::unification::Result<()> { + let SVD::SubWord { size, .. } = &value.data else { + return Ok(()); + }; + + let inferred_word = TE::bytes(Some(*size)); + state.infer_for(value, inferred_word); + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use crate::{ + inference::{ + expression::{WordUse, TE}, + rule::{masked_word::MaskedWordRule, InferenceRule}, + state::InferenceState, + }, + vm::value::{Provenance, SV, SVD}, + }; + + #[test] + fn creates_correct_inference_equations() -> anyhow::Result<()> { + // Create the values to run inference on + let value = SV::new_value(0, Provenance::Synthetic); + let mask = SV::new_synthetic( + 1, + SVD::SubWord { + value: value.clone(), + offset: 64, + size: 128, + }, + ); + + // Register them in the state + let mut state = InferenceState::empty(); + let [value_tv, mask_tv] = state.register_many([value, mask.clone()]); + + // Run the inference rule + MaskedWordRule.infer(&mask, &mut state)?; + + // Check that we end up with the correct equations + assert!(state.inferences(value_tv).is_empty()); + assert_eq!(state.inferences(mask_tv).len(), 1); + assert!( + state + .inferences(mask_tv) + .contains(&TE::word(Some(128), WordUse::Bytes)) + ); + + Ok(()) + } +} diff --git a/src/inference/rule/mod.rs b/src/inference/rule/mod.rs index f04fad4..0e7fc8d 100644 --- a/src/inference/rule/mod.rs +++ b/src/inference/rule/mod.rs @@ -5,6 +5,7 @@ pub mod arithmetic_operations; pub mod bit_shifts; pub mod boolean_operations; +pub mod call_data; pub mod condition; pub mod create; pub mod dynamic_array_write; @@ -12,6 +13,7 @@ pub mod environment_opcodes; pub mod ext_code; pub mod external_calls; pub mod mapping_access; +pub mod masked_word; pub mod offset_size; pub mod sha3; pub mod storage_key; @@ -34,12 +36,14 @@ use crate::{ arithmetic_operations::ArithmeticOperationRule, bit_shifts::BitShiftRule, boolean_operations::BooleanOpsRule, + call_data::CallDataRule, condition::ConditionRule, create::CreateContractRule, dynamic_array_write::DynamicArrayWriteRule, environment_opcodes::EnvironmentCodesRule, external_calls::ExternalCallRule, mapping_access::MappingAccessRule, + masked_word::MaskedWordRule, offset_size::OffsetSizeRule, sha3::HashRule, storage_key::StorageKeyRule, @@ -126,10 +130,12 @@ impl InferenceRules { impl Default for InferenceRules { fn default() -> Self { + // Keep these sorted for easy visual grep let mut rules = Self::new(); rules.add(ArithmeticOperationRule); rules.add(BitShiftRule); rules.add(BooleanOpsRule); + rules.add(CallDataRule); rules.add(ConditionRule); rules.add(CreateContractRule); rules.add(DynamicArrayWriteRule); @@ -137,6 +143,7 @@ impl Default for InferenceRules { rules.add(ExternalCallRule); rules.add(HashRule); rules.add(MappingAccessRule); + rules.add(MaskedWordRule); rules.add(OffsetSizeRule); rules.add(StorageKeyRule); rules.add(StorageWriteRule); diff --git a/src/inference/rule/sha3.rs b/src/inference/rule/sha3.rs index f7bb69c..dc35e98 100644 --- a/src/inference/rule/sha3.rs +++ b/src/inference/rule/sha3.rs @@ -2,7 +2,7 @@ //! opcodes. use crate::{ - constant::WORD_SIZE, + constant::WORD_SIZE_BITS, inference::{expression::TE, rule::InferenceRule, state::InferenceState}, vm::value::{BoxedVal, SVD}, }; @@ -20,11 +20,11 @@ impl InferenceRule for HashRule { ) -> crate::error::unification::Result<()> { match &value.data { SVD::Sha3 { .. } => { - state.infer_for(value, TE::bytes(Some(WORD_SIZE))); + state.infer_for(value, TE::bytes(Some(WORD_SIZE_BITS))); } SVD::ExtCodeHash { address } => { state.infer_for(address, TE::address()); - state.infer_for(value, TE::bytes(Some(WORD_SIZE))); + state.infer_for(value, TE::bytes(Some(WORD_SIZE_BITS))); } _ => (), } @@ -36,7 +36,7 @@ impl InferenceRule for HashRule { #[cfg(test)] mod test { use crate::{ - constant::WORD_SIZE, + constant::WORD_SIZE_BITS, inference::{ expression::TE, rule::{sha3::HashRule, InferenceRule}, @@ -64,7 +64,7 @@ mod test { // Check we get the correct equations assert!(state.inferences(value_tv).is_empty()); - assert!(state.inferences(hash_tv).contains(&TE::bytes(Some(WORD_SIZE)))); + assert!(state.inferences(hash_tv).contains(&TE::bytes(Some(WORD_SIZE_BITS)))); Ok(()) } @@ -88,7 +88,7 @@ mod test { // Check we get the correct equations assert!(state.inferences(value_tv).contains(&TE::address())); - assert!(state.inferences(hash_tv).contains(&TE::bytes(Some(WORD_SIZE)))); + assert!(state.inferences(hash_tv).contains(&TE::bytes(Some(WORD_SIZE_BITS)))); Ok(()) } diff --git a/src/inference/state.rs b/src/inference/state.rs index 10c9314..fbf6887 100644 --- a/src/inference/state.rs +++ b/src/inference/state.rs @@ -387,7 +387,7 @@ mod test { let value = SymbolicValue::new_value(0, Provenance::Synthetic); let type_variable = state.register(value); - let expression = TypeExpression::default_word(); + let expression = TypeExpression::bytes(None); state.infer(type_variable, expression.clone()); assert_eq!( diff --git a/src/inference/unification/mod.rs b/src/inference/unification/mod.rs index 55d370b..92cdc40 100644 --- a/src/inference/unification/mod.rs +++ b/src/inference/unification/mod.rs @@ -129,12 +129,10 @@ pub fn merge(left: TE, right: TE) -> Merge { ( TE::Word { width: width_l, - signed: signed_l, usage: usage_l, }, TE::Word { width: width_r, - signed: signed_r, usage: usage_r, }, ) => { @@ -153,35 +151,25 @@ pub fn merge(left: TE, right: TE) -> Merge { (None, None) => None, }; - let signed = match (signed_l, signed_r) { - (Some(l), Some(r)) if l == r => Some(*l), - (Some(_), Some(_)) => { - return Merge::expression(TE::conflict( - left, - right, - "Disagreeing numeric signedness", - )); - } - (Some(l), _) => Some(*l), - (_, Some(r)) => Some(*r), - (None, None) => None, + // We need to merge the usages + let Some(usage) = usage_l.merge(*usage_r) else { + return Merge::expression(TE::conflict(left, right, "Conflicting word usages")); }; - Merge::expression(TE::word( - width, - signed, - usage_l.clone().merge(usage_r.clone()), - )) + Merge::expression(TE::word(width, usage)) } // To combine a word with a dynamic array we delegate (TE::Word { .. }, TE::DynamicArray { .. }) => merge(right, left), // They produce a dynamic array as long as the word is not signed - (TE::DynamicArray { .. }, TE::Word { signed, .. }) => Merge::expression(match signed { - Some(false) | None => left, - _ => TE::conflict(left, right, "An array cannot have signed length"), - }), + (TE::DynamicArray { .. }, TE::Word { usage, .. }) => { + Merge::expression(if usage.is_definitely_signed() { + TE::conflict(left, right, "Dynamic arrays cannot have signed length") + } else { + left + }) + } // Dynamic arrays can combine with dynamic arrays (TE::DynamicArray { element: element_l }, TE::DynamicArray { element: element_r }) => { @@ -237,7 +225,7 @@ pub fn merge(left: TE, right: TE) -> Merge { (_, TE::Any) => Merge::expression(left), (TE::Any, _) => Merge::expression(right), - // Nothing else can combine and be valid, so we error + // Nothing else can combine and be valid, so we return a typing conflict _ => Merge::expression(TE::conflict(left, right, "Incompatible inferences")), } } @@ -323,7 +311,7 @@ mod test { // Set up some inferences let inference_1 = TE::eq(v_2_tv); - let inference_2 = TE::default_word(); + let inference_2 = TE::bytes(None); // Check it does the right thing let result = panic::catch_unwind(|| merge(inference_1.clone(), inference_2.clone())); @@ -341,8 +329,8 @@ mod test { let v_1_tv = state.register(v_1); // Set up some inferences - let inference_1 = TE::unsigned_word(Some(ADDRESS_WIDTH_BITS)); - let inference_2 = TE::default_word(); + let inference_1 = TE::bytes(Some(ADDRESS_WIDTH_BITS)); + let inference_2 = TE::bytes(None); let inference_3 = TE::address(); let inferences = vec![inference_1, inference_2, inference_3]; let inference_permutations: Vec> = @@ -359,7 +347,7 @@ mod test { assert!(result.is_some()); assert_eq!( result.unwrap(), - TE::word(Some(ADDRESS_WIDTH_BITS), Some(false), WordUse::Address) + TE::word(Some(ADDRESS_WIDTH_BITS), WordUse::Address) ); } } @@ -373,8 +361,8 @@ mod test { // Set up some inferences let inference_1 = TE::signed_word(Some(64)); - let inference_2 = TE::default_word(); - let inference_3 = TE::bool(); + let inference_2 = TE::bytes(None); + let inference_3 = TE::signed_word(None); let inferences = vec![inference_1, inference_2, inference_3]; let inference_permutations: Vec> = inferences.iter().permutations(inferences.len()).unique().collect(); @@ -388,10 +376,7 @@ mod test { let result = util::get_inference(v_1_tv, state.result()); assert!(result.is_some()); - assert_eq!( - result.unwrap(), - TE::word(Some(64), Some(true), WordUse::Bool) - ); + assert_eq!(result.unwrap(), TE::word(Some(64), WordUse::SignedNumeric)); } } @@ -503,7 +488,7 @@ mod test { // Create some inferences and register them let array_inference_1 = TE::DynamicArray { element: elem_1_tv }; let array_inference_2 = TE::DynamicArray { element: elem_2_tv }; - let elem_inference_1 = TE::default_word(); + let elem_inference_1 = TE::bytes(None); let elem_inference_2 = TE::signed_word(Some(64)); state.infer(elem_1_tv, elem_inference_1); state.infer(elem_2_tv, elem_inference_2); @@ -594,7 +579,7 @@ mod test { element: elem_2_tv, length: input_len, }; - let elem_inference_1 = TE::default_word(); + let elem_inference_1 = TE::bytes(None); let elem_inference_2 = TE::signed_word(Some(64)); state.infer(elem_1_tv, elem_inference_1); state.infer(elem_2_tv, elem_inference_2); @@ -690,7 +675,7 @@ mod test { element: elem_2_tv, length: U256::from(8u32), }; - let elem_inference_1 = TE::default_word(); + let elem_inference_1 = TE::bytes(None); let elem_inference_2 = TE::signed_word(Some(64)); state.infer(elem_1_tv, elem_inference_1); state.infer(elem_2_tv, elem_inference_2); diff --git a/src/layout.rs b/src/layout.rs index 1d0c4e1..3b92000 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -14,8 +14,8 @@ pub struct StorageLayout { impl StorageLayout { /// Adds a slot specified by `index` and `typ` to the storage layout. - pub fn add(&mut self, index: usize, typ: AbiType, defaulted: bool) { - let slot = StorageSlot::new(index, typ, defaulted); + pub fn add(&mut self, index: usize, offset: usize, typ: AbiType) { + let slot = StorageSlot::new(index, offset, typ); self.slots.push(slot); } @@ -39,23 +39,21 @@ pub struct StorageSlot { /// The concrete index of the storage slot in the contract. pub index: usize, + /// The offset at which the type starts within the storage slot. + /// + /// This will be 0 except in the case of structs and other packed encodings. + pub offset: usize, + #[serde(rename = "type")] /// The best-known type of the storage slot. pub typ: AbiType, - - /// A flag indicating if any defaulting took place while computing `typ`. - pub defaulted: bool, } impl StorageSlot { /// Constructs a new storage slot container for the data at `index` with /// type `typ`. #[must_use] - pub fn new(index: usize, typ: AbiType, defaulted: bool) -> Self { - Self { - index, - typ, - defaulted, - } + pub fn new(index: usize, offset: usize, typ: AbiType) -> Self { + Self { index, offset, typ } } } diff --git a/src/opcode/control.rs b/src/opcode/control.rs index d3fdb58..bfd14f2 100644 --- a/src/opcode/control.rs +++ b/src/opcode/control.rs @@ -293,7 +293,7 @@ impl Opcode for PC { // Construct the value and push it onto the stack let result = SymbolicValue::new_known_value( instruction_pointer, - KnownWord::from(instruction_pointer.to_le_bytes().to_vec()), + KnownWord::from_le(instruction_pointer), Provenance::ProgramCounter, ); stack.push(result)?; @@ -941,7 +941,7 @@ mod test { // Prepare the VM let immediate = SymbolicValue::new_known_value( 0, - KnownWord::from(vec![0x03]), // Offset of JUMPDEST in the bytes above + KnownWord::from_le(0x03u32), // Offset of JUMPDEST in the bytes above Provenance::Synthetic, ); let mut vm = @@ -1006,7 +1006,7 @@ mod test { // Prepare the VM let immediate = SymbolicValue::new_known_value( 0, - KnownWord::from(vec![0x04]), // Out of bounds + KnownWord::from_le(0x04u32), // Out of bounds Provenance::Synthetic, ); let mut vm = @@ -1041,7 +1041,7 @@ mod test { // Prepare the VM let immediate = SymbolicValue::new_known_value( 0, - KnownWord::from(vec![0x03]), // The final INVALID in the bytes above + KnownWord::from_le(0x03u32), // The final INVALID in the bytes above Provenance::Synthetic, ); let mut vm = @@ -1071,7 +1071,7 @@ mod test { // Prepare the VM let immediate = SymbolicValue::new_known_value( 0, - KnownWord::from(vec![0x02]), // Offset of JUMPDEST in the bytes above + KnownWord::from_le(0x02u32), // Offset of JUMPDEST in the bytes above Provenance::Synthetic, ); let cond = SymbolicValue::new_synthetic(1, SymbolicValueData::new_value()); @@ -1124,7 +1124,7 @@ mod test { // Prepare the VM let immediate = SymbolicValue::new_known_value( 0, - KnownWord::from(vec![0x04]), // OOB target + KnownWord::from_le(0x04u32), // OOB target Provenance::Synthetic, ); let cond = SymbolicValue::new_synthetic(1, SymbolicValueData::new_value()); @@ -1162,7 +1162,7 @@ mod test { // Prepare the VM let immediate = SymbolicValue::new_known_value( 0, - KnownWord::from(vec![0x02]), // Invalid jump target + KnownWord::from_le(0x02u32), // Invalid jump target Provenance::Synthetic, ); let cond = SymbolicValue::new_synthetic(1, SymbolicValueData::new_value()); @@ -1201,7 +1201,7 @@ mod test { let value = stack.read(0)?; match &value.data { SymbolicValueData::KnownData { value, .. } => { - assert_eq!(value, &KnownWord::from(0x00u32.to_le_bytes().to_vec(),)); + assert_eq!(value, &KnownWord::from_le(0x00u32)); } _ => panic!("Invalid payload"), } diff --git a/src/opcode/logic.rs b/src/opcode/logic.rs index aba77c0..09933d0 100644 --- a/src/opcode/logic.rs +++ b/src/opcode/logic.rs @@ -582,17 +582,17 @@ impl Opcode for Byte { // Construct the constants let const_0x08 = SymbolicValue::new_known_value( instruction_pointer, - KnownWord::from(0x08u8.to_le_bytes().to_vec()), + KnownWord::from_le(0x08u8), Provenance::Bytecode, ); let const_0xf8 = SymbolicValue::new_known_value( instruction_pointer, - KnownWord::from(0xf8u8.to_le_bytes().to_vec()), + KnownWord::from_le(0xf8u8), Provenance::Bytecode, ); let const_0xff = SymbolicValue::new_known_value( instruction_pointer, - KnownWord::from(0xffu8.to_le_bytes().to_vec()), + KnownWord::from_le(0xffu8), Provenance::Bytecode, ); @@ -1142,7 +1142,7 @@ mod test { // The right operand should be a constant 0xff match &right.data { SymbolicValueData::KnownData { value, .. } => { - assert_eq!(value, &KnownWord::from(0xffu8.to_le_bytes().to_vec(),)); + assert_eq!(value, &KnownWord::from_le(0xffu8)); } _ => panic!("Invalid payload"), } @@ -1164,10 +1164,7 @@ mod test { // The left operand is a constant 0xf8 match &left.data { SymbolicValueData::KnownData { value, .. } => { - assert_eq!( - value, - &KnownWord::from(0xf8u8.to_le_bytes().to_vec(),) - ); + assert_eq!(value, &KnownWord::from_le(0xf8u8)); } _ => panic!("Invalid payload"), } @@ -1183,10 +1180,7 @@ mod test { // The right is a constant 0x08 match &right.data { SymbolicValueData::KnownData { value, .. } => { - assert_eq!( - value, - &KnownWord::from(0x08u8.to_le_bytes().to_vec()) - ); + assert_eq!(value, &KnownWord::from_le(0x08u8)); } _ => panic!("Invalid payload"), } diff --git a/src/opcode/memory.rs b/src/opcode/memory.rs index 9c8990a..8a540f6 100644 --- a/src/opcode/memory.rs +++ b/src/opcode/memory.rs @@ -1,5 +1,7 @@ //! Opcodes that perform operations on memory or stack on the EVM. +use std::mem; + use crate::{ constant::{ DUP_OPCODE_BASE_VALUE, @@ -48,7 +50,7 @@ impl Opcode for CallDataLoad { // Construct a constant representing the word read let size = SymbolicValue::new_known_value( instruction_pointer, - KnownWord::from(32u8.to_le_bytes().to_vec()), + KnownWord::from_le(32u8), Provenance::Synthetic, ); @@ -256,7 +258,7 @@ impl Opcode for CodeSize { // Construct the value let code_size_constant = SymbolicValue::new_known_value( instruction_pointer, - KnownWord::from(true_code_size.to_le_bytes().to_vec()), + KnownWord::from(true_code_size), Provenance::Execution, ); @@ -1195,6 +1197,21 @@ impl PushN { pub fn bytes_data(&self) -> &[u8] { &self.bytes } + + /// Gets the bytes that are pushed as a known word. + #[must_use] + pub fn bytes_as_word(&self) -> KnownWord { + // Get the bytes we have and extend them out to be long enough + let mut bytes = self.bytes.clone(); + bytes.resize(mem::size_of::(), 0x0); + + // This gives us a word we want to interpret as LE + let bytes: [u8; mem::size_of::()] = bytes + .as_slice() + .try_into() + .expect("A known size array was not of that size"); + KnownWord::from_le_bytes(bytes) + } } impl Opcode for PushN { @@ -1204,12 +1221,12 @@ impl Opcode for PushN { let mut stack = vm.stack_handle()?; // Pull the data out of the opcode; validation is done in parsing - let item_data = self.bytes.clone(); + let item_data = self.bytes_as_word(); // Construct the value to push let item = SymbolicValue::new( instruction_pointer, - SymbolicValueData::new_known(KnownWord::from(item_data)), + SymbolicValueData::new_known(item_data), Provenance::Bytecode, ); @@ -1507,10 +1524,7 @@ mod test { assert_eq!(value.provenance, Provenance::Execution); match &value.data { SymbolicValueData::KnownData { value, .. } => { - assert_eq!( - value, - &KnownWord::from(code_size_actual.to_le_bytes().to_vec(),) - ); + assert_eq!(value, &KnownWord::from(code_size_actual)); } _ => panic!("Incorrect data payload"), } @@ -1843,6 +1857,7 @@ mod test { } #[test] + #[allow(unreachable_code)] fn push_n_pushes_encoded_value_onto_stack() -> anyhow::Result<()> { // We want to test for all valid push sizes for byte_count in 1..=32 { @@ -1869,7 +1884,7 @@ mod test { assert_eq!(value.provenance, Provenance::Bytecode); match &value.data { SymbolicValueData::KnownData { value, .. } => { - assert_eq!(value, &KnownWord::from(bytes)); + assert_eq!(value, &opcode.bytes_as_word()); } _ => panic!("Incorrect payload"), }; @@ -1919,7 +1934,7 @@ mod test { SymbolicValue::new_synthetic(0, SymbolicValueData::new_known(KnownWord::zero())); let item_to_swap = SymbolicValue::new_synthetic( 1, - SymbolicValueData::new_known(KnownWord::from(1u32.to_le_bytes().to_vec())), + SymbolicValueData::new_known(KnownWord::from_le(1u32)), ); let other_item = SymbolicValue::new_synthetic(1, SymbolicValueData::new_value()); diff --git a/src/opcode/util.rs b/src/opcode/util.rs index 71bed9a..8882e55 100644 --- a/src/opcode/util.rs +++ b/src/opcode/util.rs @@ -1,10 +1,6 @@ //! This file contains utilities for implementing the symbolic executable //! semantics of the opcodes. -use std::mem; - -use ethnum::U256; - use crate::{ error::{container::Locatable, execution}, opcode::control::JumpDest, @@ -35,7 +31,7 @@ use crate::{ pub fn validate_jump_destination(counter: &BoxedVal, vm: &mut VM) -> execution::Result { let instruction_pointer = vm.instruction_pointer()?; let jump_target = match &counter.clone().constant_fold().data { - SymbolicValueData::KnownData { value, .. } => value.into(), + SymbolicValueData::KnownData { value, .. } => value.value_le().as_u32(), _ => { return Err(execution::Error::NoConcreteJumpDestination.locate(instruction_pointer)); } @@ -59,75 +55,3 @@ pub fn validate_jump_destination(counter: &BoxedVal, vm: &mut VM) -> execution:: Ok(jump_target) } - -/// Provides a generic way to convert to an integral type from data contained in -/// the first `N` bytes of a byte buffer. -/// -/// # Note -/// -/// This trait could be greatly improved with the advent of the -/// [`generic_const_exprs`](https://github.com/rust-lang/rust/issues/76560) -/// feature, but that is not stable yet. -/// -/// In particular, an associated const could be used, removing the need to -/// duplicate the const generic parameter in each implementation. -trait IntegerFromBytes -where - Self: Sized, -{ - /// Converts to the specified `Self` integral type by taking the first `N` - /// items in `bytes`. - /// - /// Returns [`None`] if `bytes` does not contain >= `N` bytes. - fn from_bytes<'a>(bytes: impl Into<&'a [u8]>) -> Option { - let bytes = bytes.into(); - - if bytes.len() < N { - return None; - } - - let mut buf: [u8; N] = [0; N]; - buf[..N].copy_from_slice(&bytes[..N]); - - Some(Self::from_le_bytes(buf)) - } - - /// Converts to the type from a fixed number of bytes. - fn from_le_bytes(bytes: [u8; N]) -> Self; -} - -impl IntegerFromBytes<{ mem::size_of::() }> for u8 { - fn from_le_bytes(bytes: [u8; mem::size_of::()]) -> Self { - u8::from_le_bytes(bytes) - } -} - -impl IntegerFromBytes<{ mem::size_of::() }> for u16 { - fn from_le_bytes(bytes: [u8; mem::size_of::()]) -> Self { - u16::from_le_bytes(bytes) - } -} - -impl IntegerFromBytes<{ mem::size_of::() }> for u32 { - fn from_le_bytes(bytes: [u8; mem::size_of::()]) -> Self { - u32::from_le_bytes(bytes) - } -} - -impl IntegerFromBytes<{ mem::size_of::() }> for u64 { - fn from_le_bytes(bytes: [u8; mem::size_of::()]) -> Self { - u64::from_le_bytes(bytes) - } -} - -impl IntegerFromBytes<{ mem::size_of::() }> for u128 { - fn from_le_bytes(bytes: [u8; mem::size_of::()]) -> Self { - u128::from_le_bytes(bytes) - } -} - -impl IntegerFromBytes<{ mem::size_of::() }> for U256 { - fn from_le_bytes(bytes: [u8; mem::size_of::()]) -> Self { - U256::from_le_bytes(bytes) - } -} diff --git a/src/vm/state/memory.rs b/src/vm/state/memory.rs index 823f5eb..3930345 100644 --- a/src/vm/state/memory.rs +++ b/src/vm/state/memory.rs @@ -3,7 +3,7 @@ use std::{collections::HashMap, hash::Hash}; use crate::{ - constant::WORD_SIZE, + constant::WORD_SIZE_BITS, vm::value::{known::KnownWord, BoxedVal, Provenance, SymbolicValue, SymbolicValueData}, }; @@ -46,7 +46,7 @@ impl Memory { } } - /// Stores the provided `value` (of size [`WORD_SIZE`]) at the provided + /// Stores the provided `value` (of size [`WORD_SIZE_BITS`]) at the provided /// `offset` in the memory. /// /// This will overwrite any existing value at the provided `offset`. @@ -302,7 +302,7 @@ impl MemStoreSize { pub fn bits_count(&self) -> usize { match self { MemStoreSize::Byte => 8, - MemStoreSize::Word => WORD_SIZE, + MemStoreSize::Word => WORD_SIZE_BITS, } } } diff --git a/src/vm/state/stack.rs b/src/vm/state/stack.rs index 9ccaf81..5e085e4 100644 --- a/src/vm/state/stack.rs +++ b/src/vm/state/stack.rs @@ -20,7 +20,7 @@ use crate::{ /// # Depth /// /// In a true EVM, it is a depth [`MAXIMUM_STACK_DEPTH`] stack, where each item -/// is of size [`crate::constant::WORD_SIZE`]. Here, the symbolic virtual +/// is of size [`crate::constant::WORD_SIZE_BITS`]. Here, the symbolic virtual /// machine maintains the same maximum depth, but instead stores /// [`crate::vm::value::SymbolicValue`]s instead of words. #[derive(Clone, Debug, Default, Eq, PartialEq)] diff --git a/src/vm/value/known.rs b/src/vm/value/known.rs index 7c649cb..1315cce 100644 --- a/src/vm/value/known.rs +++ b/src/vm/value/known.rs @@ -1,15 +1,36 @@ -use std::fmt::{Display, Formatter}; +#![cfg(target_endian = "little")] +//! This module contains a representation of concrete word values for the EVM. +//! +//! # Endianness +//! +//! As this module currently assumes a platform endianness of little-endian, it +//! will intentionally fail to compile if compiled for a big-endian architecture +//! in order to prevent subtle bugs. -use ethnum::U256; +use std::{ + fmt::{Display, Formatter}, + mem, +}; + +use bitvec::{ + prelude::{BitVec, Lsb0}, + view::BitView, +}; +use ethnum::{I256, U256}; /// The type of data whose value is concretely known during symbolic execution. /// -/// It is assumed that all byte sequences are encoded in little-endian ordering. -/// This is _not_ the EVM convention, as it uses network byte ordering, but -/// here there is no interaction with a real EVM. +/// # Endianness +/// +/// Internally, the value of the word is stored in platform (little-endian) +/// endianness (see above). /// -/// For more information on the concrete definition of these data types, please -/// see [`crate::inference::abi::AbiType`]. +/// When altering this code, be very careful to understand which operations on +/// the underlying [`U256`] actually alter the underlying data based on that +/// platform assumption. Functions like `to_{le,be}` and +/// `{to,from}_{le,be}_bytes` perform no conversion when converting to and from +/// platform endianness (LE in our case), but functions like `swap_bytes` will +/// always swap. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct KnownWord { value: U256, @@ -20,97 +41,406 @@ impl KnownWord { /// integer. #[must_use] pub fn zero() -> Self { - Self::from(vec![0]) + Self::from_le(0x0u8) + } + + /// Constructs a new `KnownWord` from `value` where the value is using + /// little-endian byte ordering. + #[must_use] + pub fn from_le(value: impl Into) -> Self { + // If it is LE we already have the correct byte ordering, so we just convert + let value = value.into(); + Self { value } + } + + /// Constructs a new `KnownWord` from `value` where the value is using + /// big-endian byte ordering. + #[must_use] + pub fn from_be(value: impl Into) -> Self { + // This unconditionally swaps the byte ordering to match the platform + // endianness. + let value = value.into().swap_bytes(); + Self { value } + } + + /// Constructs a new `KnownWord` from `value` where the value is using + /// little-endian byte ordering. + #[must_use] + pub fn from_le_signed(value: impl Into) -> Self { + // As it is LE we already have the correct byte ordering, so we can just + // reinterpret the bytes directly + let value = U256::from_ne_bytes(value.into().to_ne_bytes()); + Self { value } + } + + /// Constructs a new `KnownWord` from `value` where the value is using + /// big-endian byte ordering. + #[must_use] + pub fn from_be_signed(value: impl Into) -> Self { + // Here we want to reinterpret the existing (claimed big-endian) bytes into + // little endian, so we convert on ingest but take the pattern as is otherwise + let value = U256::from_be_bytes(value.into().to_ne_bytes()); + Self { value } + } + + /// Constructs a new `KnownWord` from `bytes` where the bytes use + /// little-endian ordering. + #[must_use] + pub fn from_le_bytes(bytes: impl Into<[u8; mem::size_of::()]>) -> Self { + // Here we can take the bytes in the order they come in + let value = U256::from_le_bytes(bytes.into()); + Self { value } + } + + /// Constructs a new `KnownWord` from `bytes` where the bytes use + /// big-endian ordering. + #[must_use] + pub fn from_be_bytes(bytes: impl Into<[u8; mem::size_of::()]>) -> Self { + // Here we have to convert the order through use of `from_be_bytes` + let value = U256::from_be_bytes(bytes.into()); + Self { value } } - /// Gets the value of the known word. + /// Gets the value of the known word using little-endian byte ordering. #[must_use] - pub fn value(&self) -> U256 { + pub fn value_le(&self) -> U256 { self.value } + + /// Gets the value of the known word using little-endian byte ordering and + /// interpreting the bit pattern as a signed number. + #[must_use] + pub fn value_le_signed(&self) -> I256 { + // Things are LE internally, so we just reinterpret + I256::from_ne_bytes(self.value.to_ne_bytes()) + } + + /// Gets the value of the known word using network byte ordering. + #[must_use] + pub fn value_be(&self) -> U256 { + // In this case we want to return the value with flipped endianness + self.value.to_be() + } + + /// Gets the value of the known word using network byte ordering and + /// interpreting the bit pattern as a signed number. + #[must_use] + pub fn value_be_signed(&self) -> I256 { + // We want to flip the endianness via `to_be_bytes`, but then we want to + // interpret the bytes directly, hence `from_ne_bytes` + I256::from_ne_bytes(self.value.to_be_bytes()) + } + + /// Gets the low-level bits of this known word in little endian ordering. + #[must_use] + pub fn bits_le(&self) -> BitVec { + // A no-op, but this is correct + let bytes_le = self.value.to_le_bytes(); + + let mut bits = BitVec::new(); + for byte in bytes_le { + bits.extend(byte.view_bits::()); + } + + bits + } + + /// Gets the low-level bits of this known word in big endian ordering. + #[must_use] + pub fn bits_be(&self) -> BitVec { + // We have to swap the byte order here + let bytes_le = self.value.to_be_bytes(); + + let mut bits = BitVec::new(); + for byte in bytes_le { + bits.extend(byte.view_bits::()); + } + + bits + } + + /// Performs signed division of two known words. + #[must_use] + pub fn signed_div(self, rhs: Self) -> Self { + // In order to get signed behaviour we reinterpret the byte patterns into I256 + // before performing the operation using `{to,from}_ne_bytes` + let left_signed = I256::from_ne_bytes(self.value.to_ne_bytes()); + let right_signed = I256::from_ne_bytes(rhs.value.to_ne_bytes()); + let result = left_signed / right_signed; + + // To convert it back internally we need to perform direct conversion of the + // byte pattern, using native endianness + KnownWord::from_le(U256::from_ne_bytes(result.to_ne_bytes())) + } + + /// Performs unsigned modulo of two known words. + #[must_use] + pub fn modulo(self, rhs: Self) -> Self { + // The operation takes place in native endianness, which in our case is LE + KnownWord::from_le(self.value % rhs.value) + } + + /// Performs signed modulo of two known words. + #[must_use] + pub fn signed_mod(self, rhs: Self) -> Self { + // In order to get signed behaviour we reinterpret the byte patterns into I256 + // before performing the operation using `{to,from}_ne_bytes` + let left_signed = I256::from_ne_bytes(self.value.to_ne_bytes()); + let right_signed = I256::from_ne_bytes(rhs.value.to_ne_bytes()); + let result = left_signed % right_signed; + + // To convert it back internally we need to perform direct conversion of the + // byte pattern, using native endianness + KnownWord::from_le(U256::from_ne_bytes(result.to_ne_bytes())) + } + + /// Performs exponentiation of two known words. + #[must_use] + pub fn exp(self, rhs: Self) -> Self { + // The operation takes place in native endianness, which in our case is LE + KnownWord::from_le(self.value.pow(rhs.value.as_u32())) + } + + /// Computes less-than of two known words. + #[must_use] + pub fn lt(self, rhs: Self) -> Self { + // Bool to KnownWord is a fixed operation + KnownWord::from(self.value < rhs.value) + } + + /// Computes greater-than of two known words. + #[must_use] + pub fn gt(self, rhs: Self) -> Self { + // Bool to KnownWord is a fixed operation + KnownWord::from(self.value > rhs.value) + } + + /// Computes signed less-than of two known words. + #[must_use] + pub fn signed_lt(self, rhs: Self) -> Self { + // In order to get signed behaviour we reinterpret the byte patterns into I256 + // before performing the operation using `{to,from}_ne_bytes` + let left_signed = I256::from_ne_bytes(self.value.to_ne_bytes()); + let right_signed = I256::from_ne_bytes(rhs.value.to_ne_bytes()); + let result = left_signed < right_signed; + + // Converting it back in this case is simple as + KnownWord::from(result) + } + + /// Computes signed less-than of two known words. + #[must_use] + pub fn signed_gt(self, rhs: Self) -> Self { + // In order to get signed behaviour we reinterpret the byte patterns into I256 + // before performing the operation using `{to,from}_ne_bytes` + let left_signed = I256::from_ne_bytes(self.value.to_ne_bytes()); + let right_signed = I256::from_ne_bytes(rhs.value.to_ne_bytes()); + let result = left_signed > right_signed; + + // Converting it back in this case is simple as + KnownWord::from(result) + } + + /// Computes equality of two known words. + #[must_use] + pub fn eq(self, rhs: Self) -> Self { + // This does not care about endianness, as it just compares the bit pattern + // directly + KnownWord::from(self.value == rhs.value) + } + + /// Checks if `self` is zero. + #[must_use] + pub fn is_zero(self) -> Self { + // This does not care about endianness, as it just compares the bit pattern + // directly + KnownWord::from(self == Self::zero()) + } + + /// Computes the signed right shift of `self` by `rhs`. + #[must_use] + pub fn sar(self, rhs: Self) -> Self { + // We need the value to be signed to make it an arithmetic shift + let result = self.value_le_signed() >> rhs.value_le(); + + // We are already LE, but need to turn it back into the unsigned internal rep + KnownWord::from_le_signed(result) + } } -/// Constructs a known word from an array of little-endian bytes. -impl From> for KnownWord { - fn from(mut value: Vec) -> Self { - value.resize(32, 0); - let value: U256 = U256::from_le_bytes(value.as_slice().try_into().unwrap()); - Self { value } +impl std::ops::Add for KnownWord { + type Output = KnownWord; + + /// Performs addition of two known words. + fn add(self, rhs: KnownWord) -> Self::Output { + // The operation takes place in native endianness, which in our case is LE + KnownWord::from_le(self.value + rhs.value) } } -/// Constructs a known word from a [`usize`]. -impl From for KnownWord { - fn from(value: usize) -> Self { - let value = U256::from(value as u128); - Self { value } +impl std::ops::Mul for KnownWord { + type Output = KnownWord; + + /// Performs multiplication of two known words. + fn mul(self, rhs: KnownWord) -> Self::Output { + // The operation takes place in native endianness, which in our case is LE + KnownWord::from_le(self.value * rhs.value) + } +} + +impl std::ops::Sub for KnownWord { + type Output = KnownWord; + + /// Performs subtraction of two known words. + fn sub(self, rhs: KnownWord) -> Self::Output { + // The operation takes place in native endianness, which in our case is LE + KnownWord::from_le(self.value - rhs.value) } } -/// Constructs a known word from a [`U256`]. -impl From for KnownWord { - fn from(value: U256) -> Self { +impl std::ops::Div for KnownWord { + type Output = KnownWord; + + /// Performs unsigned division of two known words. + fn div(self, rhs: KnownWord) -> Self::Output { + // The operation takes place in native endianness, which in our case is LE + KnownWord::from_le(self.value / rhs.value) + } +} + +impl std::ops::BitAnd for KnownWord { + type Output = KnownWord; + + /// Computes bitwise and of two known words. + fn bitand(self, rhs: KnownWord) -> Self::Output { + // While the operation is endianness-agnostic, the result is still little-endian + // ordered + KnownWord::from_le(self.value & rhs.value) + } +} + +impl std::ops::BitOr for KnownWord { + type Output = KnownWord; + + /// Computes bitwise or of two known words. + fn bitor(self, rhs: KnownWord) -> Self::Output { + // While the operation is endianness-agnostic, the result is still little-endian + // ordered + KnownWord::from_le(self.value | rhs.value) + } +} + +impl std::ops::BitXor for KnownWord { + type Output = KnownWord; + + /// Computes bitwise or of two known words. + fn bitxor(self, rhs: KnownWord) -> Self::Output { + // While the operation is endianness-agnostic, the result is still little-endian + // ordered + KnownWord::from_le(self.value ^ rhs.value) + } +} + +impl std::ops::Not for KnownWord { + type Output = KnownWord; + + /// Computes the bitwise negation of `self`. + fn not(self) -> Self::Output { + // While the operation is endianness-agnostic, the result is still little-endian + // ordered + KnownWord::from_le(self.value.not()) + } +} + +impl std::ops::Shl for KnownWord { + type Output = KnownWord; + + /// Computes the left shift of `self` by `rhs`. + fn shl(self, rhs: KnownWord) -> Self::Output { + KnownWord::from_le(self.value_le() << rhs.value_le()) + } +} + +impl std::ops::Shr for KnownWord { + type Output = KnownWord; + + /// Computes the unsigned right shift of `self` by `rhs`. + fn shr(self, rhs: KnownWord) -> Self::Output { + KnownWord::from_le(self.value_le() >> rhs.value_le()) + } +} + +impl From for KnownWord { + /// Constructs a known word from a [`usize`], where the `value` uses the + /// platform byte order. + fn from(value: usize) -> Self { + let value = U256::from(value.to_le() as u128); Self { value } } } -/// Obtains a [`U256`] from a known word. impl From for U256 { + /// Obtains a [`U256`] from a known word. fn from(value: KnownWord) -> Self { value.value } } -/// Obtains a [`u32`] from a known word. impl From for u32 { + /// Obtains a [`u32`] from a known word. fn from(value: KnownWord) -> Self { value.value.as_u32() } } -/// Obtains a [`u32`] from a known word. impl From<&KnownWord> for u32 { + /// Obtains a [`u32`] from a known word. fn from(value: &KnownWord) -> Self { value.value.as_u32() } } -/// Obtains a [`usize`] from a known word. impl From for usize { + /// Obtains a [`usize`] from a known word. fn from(value: KnownWord) -> Self { value.value.as_usize() } } -/// Obtains a [`usize`] from a known word. impl From<&KnownWord> for usize { + /// Obtains a [`usize`] from a known word. fn from(value: &KnownWord) -> Self { value.value.as_usize() } } -/// Obtains a [`bool`] from a known word. impl From for bool { + /// Obtains a [`bool`] from a known word. fn from(value: KnownWord) -> Self { value.value != U256::from(0u8) } } -/// Obtains a [`bool`] from a known word. impl From<&KnownWord> for bool { + /// Obtains a [`bool`] from a known word. fn from(value: &KnownWord) -> Self { value.value != U256::from(0u8) } } -/// Obtains a known word from a [`bool`]. impl From for KnownWord { + /// Obtains a known word from a [`bool`]. fn from(value: bool) -> Self { - if value { Self::from(1) } else { Self::from(0) } + if value { + Self::from_le(1u8) + } else { + Self::from_le(0u8) + } } } -/// Pretty-prints the known word as a hexadecimal-encoded number. +/// Pretty-prints the known word as a hexadecimal-encoded number using +/// big-endian byte ordering as it is easier for humans to work with. impl Display for KnownWord { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let str = hex::encode(self.value.to_be_bytes()); @@ -119,3 +449,279 @@ impl Display for KnownWord { write!(f, "0x{str}") } } + +#[cfg(test)] +mod test { + use ethnum::{I256, U256}; + + use crate::vm::value::known::KnownWord; + + #[test] + #[allow(clippy::similar_names)] // They make sense here + fn can_construct_from_unsigned_value() { + let value_le = U256::from(0x1u32).to_le(); + let value_be = value_le.swap_bytes(); + + let word_from_le = KnownWord::from_le(value_le); + let word_from_be = KnownWord::from_be(value_be); + + assert_eq!(word_from_be, word_from_le); + assert_eq!(word_from_le.value_le(), value_le); + assert_eq!(word_from_le.value_be(), value_be); + } + + #[test] + #[allow(clippy::similar_names)] // They make sense here + fn can_construct_from_signed_value() { + let value_le = I256::from(0x1u32).to_le(); + let value_be = value_le.swap_bytes(); + + let word_from_le = KnownWord::from_le_signed(value_le); + let word_from_be = KnownWord::from_be_signed(value_be); + + assert_eq!(word_from_be, word_from_le); + assert_eq!(word_from_le.value_le_signed(), value_le); + assert_eq!(word_from_le.value_be_signed(), value_be); + } + + #[test] + #[allow(clippy::similar_names)] // They make sense here + fn can_construct_from_bytes() { + let value = U256::from(0x1u32); + let value_le_bytes = value.to_le_bytes(); + let value_be_bytes = value.to_be_bytes(); + + let word_from_le = KnownWord::from_le_bytes(value_le_bytes); + let word_from_be = KnownWord::from_be_bytes(value_be_bytes); + + assert_eq!(word_from_be, word_from_le); + assert_eq!(word_from_le.value_le().to_ne_bytes(), value_le_bytes); + assert_eq!(word_from_le.value_be().to_ne_bytes(), value_be_bytes); + } + + #[test] + fn can_get_bits_le() { + let word = KnownWord::from_le(0x1u32); + let bits = word.bits_le(); + + // It should be 256 bits long + assert_eq!(bits.len(), 256); + + // Only the 1st bit should be 1 in little-endian + for (ix, bit) in bits.iter().enumerate() { + if ix == 0 { + assert!(bit); + } else { + assert!(!bit); + } + } + } + + #[test] + fn can_get_bits_be() { + let word = KnownWord::from_le(0x1u32); + let bits = word.bits_be(); + + // It should be 256 bits long + assert_eq!(bits.len(), 256); + + // Only the 249th bit should be 1 in little-endian + for (ix, bit) in bits.iter().enumerate() { + if ix == 248 { + assert!(bit); + } else { + assert!(!bit); + } + } + } + + #[test] + fn arithmetic_can_add_known_words() { + let left = KnownWord::from_le(0x1u32); + let right = KnownWord::from_le(0x7u32); + + assert_eq!(left + right, KnownWord::from_le(0x8u32)); + } + + #[test] + fn arithmetic_can_multiply_known_words() { + let left = KnownWord::from_le(0x1u32); + let right = KnownWord::from_le(0x7u32); + + assert_eq!(left * right, KnownWord::from_le(0x7u32)); + } + + #[test] + fn arithmetic_can_subtract_known_words() { + let left = KnownWord::from_le(0x7u32); + let right = KnownWord::from_le(0x1u32); + + assert_eq!(left - right, KnownWord::from_le(0x6u32)); + } + + #[test] + fn arithmetic_can_divide_known_words() { + let left = KnownWord::from_le(0x8u32); + let right = KnownWord::from_le(0x2u32); + + assert_eq!(left / right, KnownWord::from_le(0x4u32)); + } + + #[test] + fn arithmetic_can_signed_divide_known_words() { + let left_int = I256::from(-8i16).to_le(); + let left = KnownWord::from_le_bytes(left_int.to_le_bytes()); + let right = KnownWord::from_le(0x2u32); + let result_int = I256::from(-4i16).to_le(); + let result = KnownWord::from_le_bytes(result_int.to_le_bytes()); + + assert_eq!(left.signed_div(right), result); + } + + #[test] + fn arithmetic_can_mod_known_words() { + let left = KnownWord::from_le(0x7u32); + let right = KnownWord::from_le(0x2u32); + + assert_eq!(left.modulo(right), KnownWord::from_le(0x1u32)); + } + + #[test] + fn arithmetic_can_signed_mod_known_words() { + let left_int = I256::from(-8i16).to_le(); + let left = KnownWord::from_le_bytes(left_int.to_le_bytes()); + let right = KnownWord::from_le(0x3u32); + let result_int = I256::from(-2i16).to_le(); + let result = KnownWord::from_le_bytes(result_int.to_le_bytes()); + + assert_eq!(left.signed_mod(right), result); + } + + #[test] + fn arithmetic_can_exp_known_words() { + let left = KnownWord::from_le(0x7u32); + let right = KnownWord::from_le(0x2u32); + + assert_eq!(left.exp(right), KnownWord::from_le(49u32)); + } + + #[test] + fn arithmetic_can_lt_known_words() { + let left = KnownWord::from_le(0x7u32); + let right = KnownWord::from_le(0x2u32); + + assert_eq!(left.lt(right), KnownWord::from(false)); + } + + #[test] + fn arithmetic_can_gt_known_words() { + let left = KnownWord::from_le(0x7u32); + let right = KnownWord::from_le(0x2u32); + + assert_eq!(left.gt(right), KnownWord::from(true)); + } + + #[test] + fn arithmetic_can_signed_lt_known_words() { + let left_int = I256::from(-8i16).to_le(); + let left = KnownWord::from_le_bytes(left_int.to_le_bytes()); + let right = KnownWord::from_le(0x3u32); + + assert_eq!(left.signed_lt(right), KnownWord::from(true)); + } + + #[test] + fn arithmetic_can_signed_gt_known_words() { + let left_int = I256::from(-8i16).to_le(); + let left = KnownWord::from_le_bytes(left_int.to_le_bytes()); + let right = KnownWord::from_le(0x3u32); + + assert_eq!(left.signed_gt(right), KnownWord::from(false)); + } + + #[test] + fn arithmetic_can_eq_known_words() { + let left = KnownWord::from_le(0x7u32); + let right = KnownWord::from_le(0x2u32); + + assert_eq!(left.eq(right), KnownWord::from(false)); + } + + #[test] + fn arithmetic_can_check_if_zero_for_known_word() { + assert_eq!(KnownWord::from_le(0x7u32).is_zero(), KnownWord::from(false)); + assert_eq!(KnownWord::zero().is_zero(), KnownWord::from(true)); + } + + #[test] + fn arithmetic_can_and_known_words() { + let left = KnownWord::from_le(0x7u32); + let right = KnownWord::from_le(0x2u32); + + assert_eq!( + left & right, + KnownWord::from_le(U256::from(0x7u32) & U256::from(0x2u32)) + ); + } + + #[test] + fn arithmetic_can_or_known_words() { + let left = KnownWord::from_le(0x7u32); + let right = KnownWord::from_le(0x2u32); + + assert_eq!( + left | right, + KnownWord::from_le(U256::from(0x7u32) | U256::from(0x2u32)) + ); + } + + #[test] + fn arithmetic_can_xor_known_words() { + let left = KnownWord::from_le(0x7u32); + let right = KnownWord::from_le(0x2u32); + + assert_eq!( + left ^ right, + KnownWord::from_le(U256::from(0x7u32) ^ U256::from(0x2u32)) + ); + } + + #[test] + fn arithmetic_can_negate_known_word() { + let word = KnownWord::from_le_bytes([ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ]); + let negated = KnownWord::from_le_bytes([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + ]); + assert_eq!(!word, negated); + } + + #[test] + fn arithmetic_can_shift_left() { + let shift = KnownWord::from_le(0x4u32); + let value = KnownWord::from_le(0b1110u32); + + assert_eq!(value << shift, KnownWord::from_le_signed(0b1110_0000)); + } + + #[test] + fn arithmetic_can_shift_right() { + let shift = KnownWord::from_le(2u32); + let value = KnownWord::from_le(0b10_1100u32); + + assert_eq!(value >> shift, KnownWord::from_le(0b1011u32)); + } + + #[test] + fn arithmetic_can_shift_right_signed() { + let shift = KnownWord::from_le(0x2u32); + let value = KnownWord::from_le_signed(0b1101_0100i32); + + assert_eq!(value.sar(shift), KnownWord::from_le_signed(0b11_0101i32)); + } +} diff --git a/src/vm/value/mod.rs b/src/vm/value/mod.rs index 3473aba..a0cca1d 100644 --- a/src/vm/value/mod.rs +++ b/src/vm/value/mod.rs @@ -6,7 +6,6 @@ pub mod known; use std::fmt::{Display, Formatter}; use derivative::Derivative; -use ethnum::{I256, U256}; use uuid::Uuid; use crate::vm::value::known::KnownWord; @@ -154,9 +153,9 @@ impl SymbolicValue { /// Converts the payload into a VM word if possible. #[must_use] - pub fn as_word(&self) -> Option { + pub fn as_word(&self) -> Option { match &self.data { - SymbolicValueData::KnownData { value } => Some(value.value()), + SymbolicValueData::KnownData { value } => Some(*value), _ => None, } } @@ -431,8 +430,11 @@ pub enum SymbolicValueData { /// The value is an access to a dynamic array in `slot` at `index`. DynamicArrayAccess { slot: BoxedVal, index: BoxedVal }, - /// An operation that masks `value` to by `mask`. - WordMask { value: BoxedVal, mask: usize }, + /// An operation that masks `value` to construct a sub-word value. + /// + /// The sub-word value begins at `offset` (0-based in bits where 0 is the + /// LSB) in the overarching word, and with `size` (in bits). + SubWord { value: BoxedVal, offset: usize, size: usize }, } impl SymbolicValueData { @@ -442,14 +444,6 @@ impl SymbolicValueData { SymbolicValueData::KnownData { value } } - /// Constructs a new [`Self::KnownData`] wrapping `value`. - #[must_use] - fn known_from(value: U256) -> Self { - Self::KnownData { - value: value.into(), - } - } - /// Constructs a new [`Self::Value`] about which only its existence and /// identity are known. #[must_use] @@ -696,9 +690,14 @@ impl SymbolicValueData { slot: slot.transform_data(transform), index: index.transform_data(transform), }, - Self::WordMask { value, mask } => Self::WordMask { + Self::SubWord { + value, + offset, + size, + } => Self::SubWord { value: value.transform_data(transform), - mask, + offset, + size, }, }, } @@ -725,7 +724,7 @@ impl SymbolicValueData { let left = left.transform_data(constant_folder); let right = right.transform_data(constant_folder); Some(match (left.as_word(), right.as_word()) { - (Some(a), Some(b)) => SVD::known_from(a + b), + (Some(a), Some(b)) => SVD::new_known(a + b), _ => SVD::Add { left, right }, }) } @@ -733,7 +732,7 @@ impl SymbolicValueData { let left = left.transform_data(constant_folder); let right = right.transform_data(constant_folder); Some(match (left.as_word(), right.as_word()) { - (Some(a), Some(b)) => SVD::known_from(a * b), + (Some(a), Some(b)) => SVD::new_known(a * b), _ => SVD::Add { left, right }, }) } @@ -741,7 +740,7 @@ impl SymbolicValueData { let left = left.transform_data(constant_folder); let right = right.transform_data(constant_folder); Some(match (left.as_word(), right.as_word()) { - (Some(a), Some(b)) => SVD::known_from(a - b), + (Some(a), Some(b)) => SVD::new_known(a - b), _ => SVD::Subtract { left, right }, }) } @@ -749,7 +748,7 @@ impl SymbolicValueData { let divisor = divisor.transform_data(constant_folder); let dividend = dividend.transform_data(constant_folder); Some(match (dividend.as_word(), divisor.as_word()) { - (Some(a), Some(b)) => SVD::known_from(a / b), + (Some(a), Some(b)) => SVD::new_known(a / b), _ => SVD::Divide { dividend, divisor }, }) } @@ -757,12 +756,7 @@ impl SymbolicValueData { let divisor = divisor.transform_data(constant_folder); let dividend = dividend.transform_data(constant_folder); Some(match (dividend.as_word(), divisor.as_word()) { - (Some(a), Some(b)) => { - let a_signed = I256::from_le_bytes(a.to_le_bytes()); - let b_signed = I256::from_le_bytes(b.to_le_bytes()); - let result = a_signed / b_signed; - SVD::known_from(U256::from_le_bytes(result.to_le_bytes())) - } + (Some(a), Some(b)) => SVD::new_known(a.signed_div(b)), _ => SVD::SignedDivide { dividend, divisor }, }) } @@ -770,7 +764,7 @@ impl SymbolicValueData { let divisor = divisor.transform_data(constant_folder); let dividend = dividend.transform_data(constant_folder); Some(match (dividend.as_word(), divisor.as_word()) { - (Some(a), Some(b)) => SVD::known_from(a % b), + (Some(a), Some(b)) => SVD::new_known(a.modulo(b)), _ => SVD::Modulo { dividend, divisor }, }) } @@ -778,20 +772,15 @@ impl SymbolicValueData { let divisor = divisor.transform_data(constant_folder); let dividend = dividend.transform_data(constant_folder); Some(match (dividend.as_word(), divisor.as_word()) { - (Some(a), Some(b)) => { - let a_signed = I256::from_le_bytes(a.to_le_bytes()); - let b_signed = I256::from_le_bytes(b.to_le_bytes()); - let result = a_signed % b_signed; - SVD::known_from(U256::from_le_bytes(result.to_le_bytes())) - } - _ => SVD::SignedModulo { dividend, divisor }, + (Some(a), Some(b)) => SVD::new_known(a.signed_mod(b)), + _ => SVD::SignedDivide { dividend, divisor }, }) } SVD::Exp { value, exponent } => { let value = value.transform_data(constant_folder); let exponent = exponent.transform_data(constant_folder); Some(match (value.as_word(), exponent.as_word()) { - (Some(a), Some(b)) => SVD::known_from(a.pow(b.as_u32())), + (Some(a), Some(b)) => SVD::new_known(a.exp(b)), _ => SVD::Exp { value, exponent }, }) } @@ -799,7 +788,7 @@ impl SymbolicValueData { let left = left.transform_data(constant_folder); let right = right.transform_data(constant_folder); Some(match (left.as_word(), right.as_word()) { - (Some(a), Some(b)) => SVD::new_known(KnownWord::from(a < b)), + (Some(a), Some(b)) => SVD::new_known(a.lt(b)), _ => SVD::LessThan { left, right }, }) } @@ -807,7 +796,7 @@ impl SymbolicValueData { let left = left.transform_data(constant_folder); let right = right.transform_data(constant_folder); Some(match (left.as_word(), right.as_word()) { - (Some(a), Some(b)) => SVD::new_known(KnownWord::from(a > b)), + (Some(a), Some(b)) => SVD::new_known(a.gt(b)), _ => SVD::LessThan { left, right }, }) } @@ -815,10 +804,7 @@ impl SymbolicValueData { let left = left.transform_data(constant_folder); let right = right.transform_data(constant_folder); Some(match (left.as_word(), right.as_word()) { - (Some(a), Some(b)) => SVD::new_known(KnownWord::from( - I256::from_le_bytes(a.to_le_bytes()) - < I256::from_le_bytes(b.to_le_bytes()), - )), + (Some(a), Some(b)) => SVD::new_known(a.signed_lt(b)), _ => SVD::LessThan { left, right }, }) } @@ -826,10 +812,7 @@ impl SymbolicValueData { let left = left.transform_data(constant_folder); let right = right.transform_data(constant_folder); Some(match (left.as_word(), right.as_word()) { - (Some(a), Some(b)) => SVD::new_known(KnownWord::from( - I256::from_le_bytes(a.to_le_bytes()) - > I256::from_le_bytes(b.to_le_bytes()), - )), + (Some(a), Some(b)) => SVD::new_known(a.signed_gt(b)), _ => SVD::LessThan { left, right }, }) } @@ -844,7 +827,7 @@ impl SymbolicValueData { SVD::IsZero { number } => { let number = number.transform_data(constant_folder); Some(match number.as_word() { - Some(a) => SVD::new_known(KnownWord::from(a == U256::from(0u8))), + Some(a) => SVD::new_known(a.is_zero()), _ => SVD::IsZero { number }, }) } @@ -852,7 +835,7 @@ impl SymbolicValueData { let left = left.transform_data(constant_folder); let right = right.transform_data(constant_folder); Some(match (left.as_word(), right.as_word()) { - (Some(a), Some(b)) => SVD::known_from(a & b), + (Some(a), Some(b)) => SVD::new_known(a & b), _ => SVD::And { left, right }, }) } @@ -860,7 +843,7 @@ impl SymbolicValueData { let left = left.transform_data(constant_folder); let right = right.transform_data(constant_folder); Some(match (left.as_word(), right.as_word()) { - (Some(a), Some(b)) => SVD::known_from(a | b), + (Some(a), Some(b)) => SVD::new_known(a | b), _ => SVD::Or { left, right }, }) } @@ -868,14 +851,14 @@ impl SymbolicValueData { let left = left.transform_data(constant_folder); let right = right.transform_data(constant_folder); Some(match (left.as_word(), right.as_word()) { - (Some(a), Some(b)) => SVD::known_from(a ^ b), + (Some(a), Some(b)) => SVD::new_known(a ^ b), _ => SVD::Xor { left, right }, }) } SVD::Not { value } => { let value = value.transform_data(constant_folder); Some(match value.as_word() { - Some(a) => SVD::known_from(!a), + Some(a) => SVD::new_known(!a), _ => SVD::Not { value }, }) } @@ -883,7 +866,7 @@ impl SymbolicValueData { let shift = shift.transform_data(constant_folder); let value = value.transform_data(constant_folder); Some(match (shift.as_word(), value.as_word()) { - (Some(s), Some(v)) => SVD::known_from(v << s), + (Some(s), Some(v)) => SVD::new_known(v << s), _ => SVD::LeftShift { shift, value }, }) } @@ -891,7 +874,7 @@ impl SymbolicValueData { let shift = shift.transform_data(constant_folder); let value = value.transform_data(constant_folder); Some(match (shift.as_word(), value.as_word()) { - (Some(s), Some(v)) => SVD::known_from(v >> s), + (Some(s), Some(v)) => SVD::new_known(v >> s), _ => SVD::RightShift { shift, value }, }) } @@ -899,10 +882,14 @@ impl SymbolicValueData { let shift = shift.transform_data(constant_folder); let value = value.transform_data(constant_folder); Some(match (shift.as_word(), value.as_word()) { - (Some(s), Some(v)) => SVD::known_from(v >> s), + (Some(s), Some(v)) => SVD::new_known(v.sar(s)), _ => SVD::RightShift { shift, value }, }) } + SVD::Concat { values } if values.len() == 1 => values + .first() + .cloned() + .map(|v| v.transform_data(constant_folder).data), _ => None, } } @@ -999,12 +986,13 @@ impl SymbolicValueData { Self::Concat { values } => values.iter().collect(), Self::MappingAccess { slot, key } => vec![slot, key], Self::DynamicArrayAccess { slot, index } => vec![slot, index], - Self::WordMask { value, .. } => vec![value], + Self::SubWord { value, .. } => vec![value], } } } impl Display for SymbolicValueData { + #[allow(clippy::too_many_lines)] // No sense in splitting it up fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { Self::Value { id } => write!(f, "{id}"), @@ -1104,7 +1092,11 @@ impl Display for SymbolicValueData { } Self::MappingAccess { slot, key } => write!(f, "mapping_ix<{slot}>[{key}]"), Self::DynamicArrayAccess { slot, index } => write!(f, "dynamic_array<{slot}>[{index}]"), - Self::WordMask { value, mask } => write!(f, "mask({value}, {mask})"), + Self::SubWord { + value, + offset, + size, + } => write!(f, "sub_word({value}, {offset}, {size})"), } } } @@ -1528,7 +1520,7 @@ mod test { folded, SymbolicValue::new_known_value( 2, - KnownWord::from(!U256::from(1u8)), + KnownWord::from_le(!U256::from(1u8)), Provenance::Synthetic ) ); @@ -1568,7 +1560,7 @@ mod test { assert_eq!( folded, - SymbolicValue::new_known_value(2, KnownWord::from(0b11), Provenance::Synthetic) + SymbolicValue::new_known_value(2, KnownWord::from_le(0b11u32), Provenance::Synthetic) ); } diff --git a/tests/packed_encodings.rs b/tests/packed_encodings.rs new file mode 100644 index 0000000..0f78871 --- /dev/null +++ b/tests/packed_encodings.rs @@ -0,0 +1,67 @@ +//! This module is an integration test that tests the library's analysis +//! capabilities on a hand-constructed contract that uses structs and packed +//! encodings. +#![cfg(test)] + +mod common; + +#[test] +fn analyses_packed_encodings() -> anyhow::Result<()> { + // Set to true if you are debugging + let should_print = false; + + // Create the analyzer + let contract_path = "./asset/PackedEncodings.json"; + let analyzer = common::new_analyzer_from(contract_path)?; + + // Disassemble + let disassembled = analyzer.disassemble()?; + + // Prepare the VM + let execution_ready = disassembled.prepare_vm()?; + + // Execute the VM and display the stored values + let executed = execution_ready.execute()?; + let results = &executed.state().execution_result; + + if should_print { + for (i, state) in results.states.iter().enumerate() { + if state.storage().keys().is_empty() { + println!("Skipping empty state {i}"); + continue; + } + + println!("=== State Number {i} ==="); + + let storage_keys = state.storage().keys(); + + for key in storage_keys { + println!(" ===== Slot ====="); + println!(" KEY: {key}"); + + let generations = state.storage().generations(key).unwrap(); + + for gen in generations { + println!(" VALUE: {gen}"); + } + } + + println!(); + } + } + + // Prepare the unifier + let unifier_read = executed.prepare_unifier(); + + // Perform unification + let unification_complete = unifier_read.infer()?; + + // Get the final storage layout for the input contract and print it for + // debugging + let layout = unification_complete.layout(); + if should_print { + dbg!(layout); + } + + Ok(()) +} diff --git a/tests/simple_contract.rs b/tests/simple_contract.rs index 5674c4f..ece7b8c 100644 --- a/tests/simple_contract.rs +++ b/tests/simple_contract.rs @@ -18,22 +18,24 @@ fn analyses_simple_contract() -> anyhow::Result<()> { // Inspect it to check that things are correct assert_eq!(layout.slots().len(), 2); - // Check that we see the `mapping(any => mapping(any => any))` + // Check that we see the `mapping(bytes16 => mapping(bytes16 => bytes32))` let expected_mapping = AbiType::Mapping { - key_tp: Box::new(AbiType::Any), + key_tp: Box::new(AbiType::Bytes { length: Some(16) }), val_tp: Box::new(AbiType::Mapping { - key_tp: Box::new(AbiType::Any), - val_tp: Box::new(AbiType::Any), + key_tp: Box::new(AbiType::Bytes { length: Some(16) }), + val_tp: Box::new(AbiType::Bytes { length: Some(32) }), }), }; - let expected_mapping_slot = StorageSlot::new(0, expected_mapping, false); + let expected_mapping_slot = StorageSlot::new(0, 0, expected_mapping); assert!(layout.slots().contains(&expected_mapping_slot)); - // Check that we see the `any[]` + // Check that we see the `uint64[]` let expected_dyn_array = AbiType::DynArray { - tp: Box::new(AbiType::Any), + // Unfortunately we can't currently work out that it's 64 bit as they use a different + // method to scale the values beyond the supported one + tp: Box::new(AbiType::UInt { size: None }), }; - let expected_array_slot = StorageSlot::new(1, expected_dyn_array, false); + let expected_array_slot = StorageSlot::new(1, 0, expected_dyn_array); assert!(layout.slots().contains(&expected_array_slot)); Ok(())