From d2da8d8e7e880b4ac5b18d91ebc09ea288b3427f Mon Sep 17 00:00:00 2001 From: Andrew Morris Date: Fri, 10 Feb 2023 16:07:01 +1100 Subject: [PATCH 1/5] PseudoFloat.sol --- contracts/contracts/lib/PseudoFloat.sol | 112 ++++++++++++++++++++++++ contracts/contracts/lib/VLQ.sol | 2 +- 2 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 contracts/contracts/lib/PseudoFloat.sol diff --git a/contracts/contracts/lib/PseudoFloat.sol b/contracts/contracts/lib/PseudoFloat.sol new file mode 100644 index 00000000..0a2a6312 --- /dev/null +++ b/contracts/contracts/lib/PseudoFloat.sol @@ -0,0 +1,112 @@ +//SPDX-License-Identifier: Unlicense +pragma solidity >=0.7.0 <0.9.0; +pragma abicoder v2; + +import "./VLQ.sol"; + +/** + * Like a float, but technically for integers. Also base 10. + * + * The pseudo-float is an encoding that can represent any uint256 value but + * efficiently represents values with a small number of significant figures + * (just 2 bytes for 3 significant figures). + * + * Zero is a special case, it's just 0x00. + * + * Otherwise, start with the value in scientific notation: + * + * 1.23 * 10^16 (e.g. 0.0123 ETH) + * + * Make the mantissa (1.23) a whole number by adjusting the exponent: + * + * 123 * 10^14 + * + * We add 1 to the exponent and encode it in 5 bits: + * + * 11111 (=15) + * + * (The maximum exponent is 30. Adjust the left side of the previous + * equation if needed.) + * + * Encode the left side in binary: + * + * 1111011 (=123) + * + * Our first byte is the 5-bit exponent followed by the three lowest bits of the + * mantissa: + * + * 11111011 + * ^^^^^-------- 15 => exponent is 14 + * ^^^----- lowest 3 bits of the mantissa + * + * Encode the remaining bits of the mantissa as a VLQ: + * + * 00001111 + * ^------------ special VLQ bit, zero indicates this is the last byte + * ^^^^^^^----- bits to use, put them together with 011 above to get + * 0001111011, which is 123. + * + * Example 2: + * + * 0.883887085 ETH uses 5 bytes: 0x55b4d7c27d + * 883887085 * 10^9 + * For exponent 9 we encode 10 as 5 bits: 01010 + * 883887085 is 110100101011110000101111101(101) + * + * 01010101 10110100 11010111 11000010 01111101 + * ^^^^^------------------------------------------- 10 => exponent is 9 + * ^^^---------------------------------------- lowest 3 bits + * ^^^^^^^--^^^^^^^--^^^^^^^--^^^^^^^ higher bits + * ^--------^--------^-------------------- 1 => not the last byte + * ^----------- 0 => the last byte + * + * Note that the *encode* process is described above for explanatory purposes. + * On-chain we need to *decode* to recover the value from the encoded binary + * instead. + */ +library PseudoFloat { + function decode( + bytes calldata stream + ) internal pure returns (uint256, bytes calldata) { + uint8 firstByte = uint8(stream[0]); + + if (firstByte == 0) { + return (0, stream[1:]); + } + + uint8 exponent = ((firstByte & 0xf8) >> 3) - 1; + + uint256 value; + (value, stream) = VLQ.decode(stream); + + value <<= 3; + value += firstByte & 0x03; + + // TODO (merge-ok): Exponentiation by squaring might be better here. + // Counterpoints: + // - The gas used is pretty low anyway + // - For these low exponents (typically ~15), the benefit is unclear + for (uint256 i = 0; i < exponent; i++) { + value *= 10; + } + + return (value, stream); + } + + /** + * Same as decode, but public. + * + * This is here because when a library function that is not internal + * requires linking when used in other contracts. This avoids including a + * copy of that function in the contract but it's complexity that we don't + * want right now. + * + * What we do want though, is a public version so that we can call it + * statically for testing. + */ + function decodePublic( + bytes calldata stream + ) public pure returns (uint256, bytes calldata) { + return decode(stream); + } +} diff --git a/contracts/contracts/lib/VLQ.sol b/contracts/contracts/lib/VLQ.sol index 436cd8bb..de434147 100644 --- a/contracts/contracts/lib/VLQ.sol +++ b/contracts/contracts/lib/VLQ.sol @@ -1,4 +1,4 @@ -//SPDX-License-Identifier: MIT +//SPDX-License-Identifier: Unlicense pragma solidity >=0.7.0 <0.9.0; pragma abicoder v2; From a666a2b9c9dd6c6bc3dfb9353cd27745417af27f Mon Sep 17 00:00:00 2001 From: Andrew Morris Date: Fri, 10 Feb 2023 16:25:31 +1100 Subject: [PATCH 2/5] Add and fix tests --- contracts/contracts/lib/PseudoFloat.sol | 14 +++++++---- contracts/test/pseudoFloat-test.ts | 33 +++++++++++++++++++++++++ contracts/test/vlq-test.ts | 2 +- 3 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 contracts/test/pseudoFloat-test.ts diff --git a/contracts/contracts/lib/PseudoFloat.sol b/contracts/contracts/lib/PseudoFloat.sol index 0a2a6312..ee04705f 100644 --- a/contracts/contracts/lib/PseudoFloat.sol +++ b/contracts/contracts/lib/PseudoFloat.sol @@ -23,7 +23,7 @@ import "./VLQ.sol"; * * We add 1 to the exponent and encode it in 5 bits: * - * 11111 (=15) + * 01111 (=15) * * (The maximum exponent is 30. Adjust the left side of the previous * equation if needed.) @@ -35,7 +35,7 @@ import "./VLQ.sol"; * Our first byte is the 5-bit exponent followed by the three lowest bits of the * mantissa: * - * 11111011 + * 01111011 * ^^^^^-------- 15 => exponent is 14 * ^^^----- lowest 3 bits of the mantissa * @@ -46,8 +46,12 @@ import "./VLQ.sol"; * ^^^^^^^----- bits to use, put them together with 011 above to get * 0001111011, which is 123. * + * Putting it together is two bytes: + * + * 0x7b0f + * * Example 2: - * + * * 0.883887085 ETH uses 5 bytes: 0x55b4d7c27d * 883887085 * 10^9 * For exponent 9 we encode 10 as 5 bits: 01010 @@ -77,10 +81,10 @@ library PseudoFloat { uint8 exponent = ((firstByte & 0xf8) >> 3) - 1; uint256 value; - (value, stream) = VLQ.decode(stream); + (value, stream) = VLQ.decode(stream[1:]); value <<= 3; - value += firstByte & 0x03; + value += firstByte & 0x07; // TODO (merge-ok): Exponentiation by squaring might be better here. // Counterpoints: diff --git a/contracts/test/pseudoFloat-test.ts b/contracts/test/pseudoFloat-test.ts new file mode 100644 index 00000000..695f4f82 --- /dev/null +++ b/contracts/test/pseudoFloat-test.ts @@ -0,0 +1,33 @@ +/* eslint-disable camelcase */ + +import { expect } from "chai"; +import { ethers } from "hardhat"; +import { PseudoFloat, PseudoFloat__factory } from "../typechain-types"; + +describe("PseudoFloat", function () { + let pseudoFloat: PseudoFloat; + + this.beforeAll(async () => { + const [eoa] = await ethers.getSigners(); + const factory = new PseudoFloat__factory(eoa); + pseudoFloat = await factory.deploy(); + }); + + it("0x00 -> 0", async () => { + const [value, stream] = await pseudoFloat.decodePublic("0x00"); + expect(value).to.eq(0); + expect(stream).to.eq("0x"); + }); + + it("0x7b0f -> 0.0123 ETH", async () => { + const [value, stream] = await pseudoFloat.decodePublic("0x7b0f"); + expect(ethers.utils.formatEther(value)).to.eq("0.0123"); + expect(stream).to.eq("0x"); + }); + + it("0x55b4d7c27d -> 0.883887085 ETH", async () => { + const [value, stream] = await pseudoFloat.decodePublic("0x55b4d7c27d"); + expect(ethers.utils.formatEther(value)).to.eq("0.883887085"); + expect(stream).to.eq("0x"); + }); +}); diff --git a/contracts/test/vlq-test.ts b/contracts/test/vlq-test.ts index 55a20415..fd70ad22 100644 --- a/contracts/test/vlq-test.ts +++ b/contracts/test/vlq-test.ts @@ -3,7 +3,7 @@ import { expect } from "chai"; import { ethers } from "hardhat"; import { VLQ, VLQ__factory } from "../typechain-types"; -describe("vlq", function () { +describe("VLQ", function () { let vlq: VLQ; this.beforeAll(async () => { From 74d31d561f3a6481739c803f15c006a0a9c22d6e Mon Sep 17 00:00:00 2001 From: Andrew Morris Date: Fri, 10 Feb 2023 16:42:10 +1100 Subject: [PATCH 3/5] encodePseudoFloat --- contracts/contracts/lib/PseudoFloat.sol | 5 ++-- contracts/shared/helpers/bundleCompression.ts | 26 ++++++++++++++++++- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/contracts/contracts/lib/PseudoFloat.sol b/contracts/contracts/lib/PseudoFloat.sol index ee04705f..f278daca 100644 --- a/contracts/contracts/lib/PseudoFloat.sol +++ b/contracts/contracts/lib/PseudoFloat.sol @@ -25,8 +25,9 @@ import "./VLQ.sol"; * * 01111 (=15) * - * (The maximum exponent is 30. Adjust the left side of the previous - * equation if needed.) + * Note: The maximum value we can encode here is 31 (11111). This means the + * maximum exponent is 30. Adjust the left side of the previous equation if + * needed. * * Encode the left side in binary: * diff --git a/contracts/shared/helpers/bundleCompression.ts b/contracts/shared/helpers/bundleCompression.ts index aeb6acc5..b6f6a4d3 100644 --- a/contracts/shared/helpers/bundleCompression.ts +++ b/contracts/shared/helpers/bundleCompression.ts @@ -56,7 +56,7 @@ function remove0x(hexString: string) { return hexString.slice(2); } -function encodeVLQ(x: BigNumberish) { +export function encodeVLQ(x: BigNumberish) { x = BigNumber.from(x); const segments: number[] = []; @@ -83,3 +83,27 @@ function encodeVLQ(x: BigNumberish) { return result; } + +export function encodePseudoFloat(x: BigNumberish) { + x = BigNumber.from(x); + + if (x.eq(0)) { + return "0x00"; + } + + let exponent = 0; + + while (x.mod(10).eq(0) && exponent < 30) { + x = x.div(10); + exponent++; + } + + const exponentBits = (exponent + 1).toString(2).padStart(5, "0"); + const lowest3Bits = x.mod(8).toNumber().toString(2).padStart(3, "0"); + + const firstByte = parseInt(`${exponentBits}${lowest3Bits}`, 2) + .toString(16) + .padStart(2, "0"); + + return hexJoin([`0x${firstByte}`, encodeVLQ(x.div(8))]); +} From 51e6666776b952317420219bac8e788160e18eb7 Mon Sep 17 00:00:00 2001 From: Andrew Morris Date: Fri, 10 Feb 2023 17:26:07 +1100 Subject: [PATCH 4/5] Add big test --- contracts/test/pseudoFloat-test.ts | 324 +++++++++++++++++++++++++++++ 1 file changed, 324 insertions(+) diff --git a/contracts/test/pseudoFloat-test.ts b/contracts/test/pseudoFloat-test.ts index 695f4f82..0288ca3a 100644 --- a/contracts/test/pseudoFloat-test.ts +++ b/contracts/test/pseudoFloat-test.ts @@ -1,7 +1,10 @@ /* eslint-disable camelcase */ import { expect } from "chai"; +import { BigNumber } from "ethers"; +import { RLP } from "ethers/lib/utils"; import { ethers } from "hardhat"; +import { encodePseudoFloat } from "../shared/helpers/bundleCompression"; import { PseudoFloat, PseudoFloat__factory } from "../typechain-types"; describe("PseudoFloat", function () { @@ -30,4 +33,325 @@ describe("PseudoFloat", function () { expect(ethers.utils.formatEther(value)).to.eq("0.883887085"); expect(stream).to.eq("0x"); }); + + it("Test tx.values from 20 blocks", async () => { + // The non-zero tx.value amounts of transactions in mainnet blocks 16472849 + // to 16472868, excluding zeros. + + const rlpEncodedTxValueSamples = [ + "0xf925d6870a1d40bbff073682b0068704229bc64a50dd88010e0198eaee000087c9", + "94bab88e400088120a871cc002000088013e2d45b49a680088021d84798d7aa80087", + "6bacaa5051dc0088019ffc683f1410008603b3cb0fa4bc8603be095cbdca87470de4", + "df8200008801aa535d3d0c00008751007aace100008740cd2ef0b40000880123cff5", + "23f50c0087eafe86ecab100088136bbd135793c00087a7b02661660000876a94d74f", + "4300008801936ae92fd50fd087470de4df8200008758d15e1762800088063eb89da4", + "ed000087670758aa7c800087ae153d89fe80008707d0e36a81800088011c37937e08", + "00008724568d2810178f875644b7c0a9f01087ae153d89fe800087dc44abe8130000", + "875543df729c0000870590c4b1d13040877fe5cf2bea000087557c95fe3d0a9b8801", + "86cc6acd4b00008719396991e7c00088013bd51fe73151008798d6758217bcc887e5", + "40d692fd6800890c77e4256863d8000087470de4df82000087470de4df82000087df", + "d22a8cd9800087982cd1740208088715d141218fbf558803782dace9d90000880429", + "d069189e000088021f567b515e800088013fbe85edc900008801621a7b4e1ab16087", + "6a00d5c0dadf438331dc65880429d069189e0000833558fd8703ea0048a75b1282b5", + "0488018de76816d80000880426dfaad05e6c0087d08b56c850c00087e35fa931a000", + "008751ca0cee4cec00880214e8348c4f000088016345785d8a0000870a15e3bec7dd", + "8488154b96e763c997c0877fe5cf2bea00008803782dace9d9000087b1a2bc2ec500", + "008904e0fbf7c4136d0000880199ec80018328008904b26dde93c5fdd0ed88019d3c", + "6507b274008801cc6e836ae4000088019174e6bb9e8000882f53f7957d3a195a8707", + "d0e36a81800087f8b0a10e470000870100ce7d8a443488136dcc951d8c000087ba2b", + "4a048462de87180887928e719587b5303ad38b800088016345785d8a000087180887", + "928e7195871550f7dca70000873a84f40db5e00c8802c68af0bb140000872386f26f", + "c100008805f916850754000088044739bb8841ee988704a8688647665787b0f876d0", + "fd156d83410d9e8801aa535d3d0c00008802c68af0bb1400008345ab158603af8205", + "f5158801cdda4faccd00008603e82a0d90fa82a7048205048802c68af0bb14000087", + "470de4df820000870678e465868b108706935b24557b108751a7d040a02f58880184", + "183c20307ec48729297ccac92400880646e08f1ab9228487234e47c27fe5bc880ea6", + "2d68d0bddfb08801063112a248200087552707f2bcb91d872386f26fc10000871b09", + "4132bda000875967f4067306458803bf3b91c95b000088011fc51222ce800087a809", + "b0e62f4c8d8830927f74c9de000087132f4579c98000876a94d74f430000871c6bf5", + "263400008707d0e36a818000871b5b1bf4c540008735cfec17b87b708801aa535d3d", + "0c0000880713e24c4373000087d529ae9e86000087a200f559c20000880429d06918", + "9e0000881c01604ee4bd0000870942d79f01f7878711c37937e0800087b1a2bc2ec5", + "000087f8b0a10e47000088063eb89da4ed000087b5303ad38b8000886e794fd07f2d", + "291a8802b300b830d04000871ee491510e2a1d873ff2e795f50000881bc16d674ec8", + "00008829a2241af62c000088016345785d8a000088019c09029f9123808705d7b6d5", + "6b491d87077de673ecbb9487868bbe764dc12688016345785d8a000088027f7d0bdb", + "92000088013fbe85edc900008701579637a959b888016345785d8a0000878ecbc7a5", + "01eb5088035834391ede800087e526688403400087e526688403400087e526688403", + "400087e63741b5720000872846082d2c80008740cd2ef0b400008803782dace9d900", + "008807aa9455ace3104088013b1ec6ae987aa88369da24836dea148802c68af0bb14", + "0000870a5caa60bd3e9687b1a2bc2ec5000088023241283b9db06087b1a2bc2ec500", + "0087b1a2bc2ec5000087b1a2bc2ec50000865af3107a400087b1a2bc2ec500008801", + "2dfb0cb5e88000880d7a7cba64ea99b08801efd8ca11c9f7e88705fdf459ee094887", + "b1a2bc2ec50000870360a817c9b800870360a817c9b8008801e2839ba83045cf87c1", + "09d97031780087d4601fcaf9a4b088018e072b7720688b87470de4df82000087cc47", + "f20295c0008780c7eddff0afbe876a94d74f4300008801cdda4faccd00008815f96b", + "66219001d0880194c8dd0ce36c1c8802d84e69f2f4800087d4cea44594d80088077e", + "772392b6000087025b651454c4be8707d0e36a8180008802fbd55c62b58000880de0", + "b6b3a76400008731bced02db000087f8b0a10e4700008810e44ac2d7b5c000872386", + "f26fc1000088019502656065000087a59ee710c7a34b87138a388a43e338890107ad", + "8f556c6c00008751b660cdd58000873da194b0e6af8f8801ddd709924a4000873c65", + "68f12e80008751b660cdd5800087f8b0a10e470000876a94d74f43000087354a6ba7", + "a180008712795f58d50000877c58508723800087470de4df82000088031126543f5c", + "800088016345785d8a000087f0631f499afd0a830b8a7a880429d069189e0000830c", + "460d870a200f559c20008704907ac3125b158802c68af0bb140000871b314439129a", + "988758d15e1762800087046aacba569c0087121e6c485ac000881a5a5a92c3dda763", + "88016345785d8a000087d8dc36206f2000870ae9e7fd96b852872386f26fc1000088", + "0ddf5055e4337fd0881bafea3ec90cd29c8801a28279d28a8000875e9bd80a5e6000", + "881582b4c9a9db0000883cc3630f46ef480087e337ff7310fc00870422e199ca4a80", + "870422e199ca4a808722f7aa4c041800870422e199ca4a808808c2bf28dc3ef00088", + "0214e8348c4f000087d9270dc2a714008704229b0d95348087d529ae9e8600008822", + "1c0a46e3ac9ea1876a94d74f43000088023b9fa3e27abd2d88058d15e17628000088", + "06f05b59d3b200008804fefa17b72400008707d0e36a81800087cf58995ea1428887", + "a200f559c2000087498a8a52d9c000874c6222d6abc00087240ccb5bd5aa5f88015f", + "b7e2704c9800877c58508723800087044364c5bb000088011c37937e08000087ecf4", + "7a281ea800880214e8348c4f00008792e8b594ac8b8c8716b33511bef40086cf0812", + "58af90881158e460913d0000880bcbce7f1b15000087f8b0a10e470000873a80727c", + "d9d6d4880650a6fb126e2ac08818fae27693b40000871672b45489256e8731bced02", + "db00008784f38d03af964987059003dfe68cc88804db732547630000880429d06918", + "9e0000870ebe79cc3080008803f374faafbe3f00880279210d016c2000881ab95aa9", + "90a06000880165b905826f20008808a2c85ceed480008788a41362b4d000880dd8ef", + "b1fd67638a8811a0839022bfbc00882aa1173ca067740087ae153d89fe80008829e9", + "31ffd5ae0000882e5b2126ff3cc00087045cdc07c8c0008727ca57357c000087470d", + "e4df82000087209dbbfdf6dc0088061b8ef6f9a208008905a9e389b70f52c000881e", + "4360bacd2e500087369310accb86f58798ca7e055a4e5788019d7915d1a498008803", + "4e6686ede3e00087b1a2bc2ec50000880280a0b8a52658008818cdccb3dc1c280088", + "066fb94b5e67d80087470de4df820000880162dfe10504af68880428a40f57413bb5", + "8904e0fad4ac64c9a72d87fcb8714bddee7a87fb5d55fee107498807a9a5cde44e55", + "6687fd2ae92851ec008801cee802c2fc429888013efa0c880a3330880153c54e35f4", + "dc6888044df2f8db17980088029a41408dae00008829a0a27bb2617d1d880147c98b", + "a0cf73f687fdc5a90791cac087fbb7e195de726a88012a60d9ec1498b087fc42722e", + "674c8087fd010e4a91440088028cf46bdd7d409887fd0e5ff4ec40b387fb8ad9bc93", + "2adb89b444990fd4b63c2800880470de4df8200000880f42a70b4d2cb220880153bd", + "edad3dd61c878200627e6924008738f5311debec0087b1a2bc2ec5000088055008c2", + "8c7a60008804fefa17b72400008801c29d16403a581087b1a2bc2ec5000087470de4", + "df820000870c1448303c800087071afd498d00008707d0e36a81800087a0f01c2853", + "4000890c6a036eb4bc74000087a6fa40407180008802c68af0bb140000880de0b6b3", + "a7640000872fed1562052000871ae7a275486bad872386f26fc100008801988fe405", + "2b8000872e2f6e5e14800087e5dc4ea4f7c000875753a518a8e22e870d1c091338a0", + "008715d233085c307987f8b0a10e47000087154b813c4d585088011c37937e080000", + "87b1a2bc2ec50000880443ba0e080dde668807abd369f80288f887468ebf3c0f7e5a", + "8803e2c284391c00008702a106ad22400087b1a2bc2ec5000088250a315be9848000", + "87ed400e6219c0008803d239ac0075f858880165c21dd0e1c0008802c68af0bb1400", + "00871ff973cafa8000879701b2a34cd890870a9679e25c1000872ba269f721a00087", + "0aa87bee53800087e304b62125c000880106fe1bf966600087068c32dbe6b8168806", + "f08882108e9d978778ef68a938fc00872f73cb2fdb4f5088012dfb0cb5e880008802", + "c68af0bb1400008807a1fe160277000087333b1ff79e73688802a5c6d0d29b4e8288", + "096eaa6f2449222087d529ae9e86000088051da3139b985657880d9a5dc72e669a68", + "8807c4ff20ca5e540088154b96e763c997c08802231e2f1f69000087d766a9ecbb60", + "0087f4f1c2476daec087e1e65de301dd1f87f8b0a10e470000871661d10e15c00088", + "06096e31fd4b8000875749e5383e864f8803311fc80a570000870aa87bee53800087", + "3161f9f260c00088011c37937e080000871f438daa06000088018a59e97211800088", + "044972afe537ff45872386f26fc10000880b1a2bc2ec500000879dbd909407000088", + "01b47e8495bfa46887753d533d968000876a94d74f430000870ffcb9e57d4000880f", + "43fc2c04ee0000872bb2c8eabcc0008829a2241af62c00008725a8a4d29e800087b1", + "a2bc2ec5000087354a6ba7a1800088013fbe85edc90000890157329bf98f69000088", + "53444835ec580000872c68af0bb140008273058818f9b8955d3c0fd88801e068812d", + "68e350878e1bc9bf040000875db10acadd602e86036c0b4dcdcc8802c68af0bb1400", + "0086038adfc64d3b858f1589e00f8802c68af0bb14000085a0fc2406f88802715a85", + "87d20c008802c68af0bb14000087525b22abb9d234880196c924b2c840008805ba34", + "76a2cdc000886124fee993bc000087aca971481580008845572247043d4000877e58", + "8abf13800087da475abf00000088068fb000035b600088076598ad11488000878e1b", + "c9bf0400008801bfa45519b300008812bb52a141f968728718838370f34000871550", + "f7dca70000872386f26fc1000087016bcc41e9000087b58b2de405c000880146d983", + "3756000087a200f559c2000087b1a2bc2ec5000087adba4a79844000871c9a5e6155", + "f1ac876ff5f7e474ef18872f24fea42b600088027f7d0bdb920000871ff973cafa80", + "008804fefa17b72400008711c37937e0800087dc5ff4d36ae0008722e9ed8d734bd5", + "8733b9a18746587887044364c5bb000088016345785d8a00008802c68af0bb140000", + "88016345785d8a0000871143d1980e0a6087354a6ba7a1800087239a108963180087", + "a877a3aa55fc0101880533702e777dc440834f4ceb8801aa535d3d0c00008350b6a7", + "82740082af00876a94d74f4300008738d7ea4c680000873ff2e795f500008801c972", + "89ad47800087778366da3e800088041b91563711600088019c1d62a9f20000880120", + "c3bab75800008804085defb0318800883aaf21ccc897ddd08726b97e040d400087cf", + "8d86bc1914008703207b6359608088013fbe85edc9000088019e5ebde27ec40087af", + "5a80c31aec0087068babd5a0880088016345785d8a000088039bb49f599a00008801", + "6345785d8a0000876d22ec4cf75c878806c26b959abfe0008810a741a46278000088", + "02a303fe4b53000086f9756f764c4186f94f39cd3e588804968de59e3af000876125", + "a099b0f5c0880853a0d2313c0000872251fff8c134208802c68af0bb140000880214", + "e8348c4f0000883782dace9d9000008802c68af0bb14000088011c37937e08000088", + "010a741a4627800087b1a2bc2ec5000088086ef0ce3a3c986f876a94d74f43000087", + "21ff6c155e4fbd878e1bc9bf040000890340aad21b3b700000871606ddfd9b800088", + "295b163616aa0000872ba269f721a0008804433881305dde9c8705de8962844f5c87", + "0c0443c94738ca870b5e620f4800008802386f26fc1000008803d5dedea5dd400088", + "058410a3fa2a94a583fb5b1a8379457188016345785d8a0000837adadd826804880f", + "43fc2c04ee00008701bb54fdf2cffb870381828d9df0fe87ae153d89fe800088039c", + "d7add005c800876a92b8f520987c88028dbc878335b40087f46d3c488c0000873bf0", + "e7562f9800870ae9664a255f3e87b1a2bc2ec50000870e35fa931a00008814d1120d", + "7b160000872e2f6e5e1480008801cd24698bd88000877c585087238000870c6f3b40", + "b6c00087470de4df8200008718de76816d800087b1a2bc2ec50000880de0b6b3a764", + "00008803bf3b91c95b00008709f295cd5f000088020324bb546e8000880237b940db", + "1b800088016345785d8a000088016345785d8a000087470de4df820000870487224a", + "84935e871c6bf526340000872386f26fc1000087051e564dcb125f8801143fb47538", + "671e875da5da5618a40087010884f37406008801c3a6c1d4d45404832a04268806f0", + "5b59d3b20000832e08268802c68af0bb14000083fb5b1b870c0e8c20457a3f828806", + "8703b0daef8d77a38806f05b59d3b20000871e8da789118000871c8aae2291712687", + "dabbde5f9a5e2887121e6c485ac000872b6563077788008802c9cbdfb0db38008738", + "c4477c20240088029ec64b0aab880088027f7d0bdb920000872b9cdaf06a0f8d8801", + "6345785d8a00008715cfffefbf7050873dd13533178000878964676019da60890c5c", + "22b80115100000880214e8348c4f0000880713e24c4373000088162c9b1002213654", + "8711c37937e0800088010a7f6a545051748751b660cdd5800088016345785d8a0000", + "876e2255f409800088012dfb0cb5e880008905bf0ba6634f6800008808be35a9807f", + "000087e5dc4ea4f7c00088061b31ab352c0000873ff2e795f5000087354a6ba7a180", + "0088011c37937e080000876a94d74f430000873099e3348700008718de76816d8000", + "87da2c620173642c88015985c7c416f00082b4068802fc26a495696432892ebaa730", + "eda2b8000083fb5b1c880214e8348c4f000083fb5b1c860c051aa407418806f05b59", + "d3b20000860c6827800c51880b1a2bc2ec5000008805e863e500e24000860b209605", + "dadd87024439e58886ad8701fd66eac8d8c88805cfd3934e8900008805e73df6cbc9", + "08008806d087062e51080087974689ffd63000872fcfc9e8696c0087129e8e9c1d38", + "fb8702c8e8ff9f020087f0321d74442019878e1bc9bf040000872386f26fc1000088", + "12091ef3d0ad40008766ac659a02400088044d575b885f000088016345785d8a0000", + "87b1a2bc2ec5000087b1a2bc2ec5000087b1a2bc2ec5000087b1a2bc2ec5000087d4", + "464ef554600088015c2a7b13fd00008802386f26fc1000008718de76816d8000880e", + "b5e06245ea0000871ff973cafa800087d4071f617f62d0880429d069189e00008711", + "c37937e08000872386f26fc100008898a7d9b8314c00008801118f178fb48000876a", + "94d74f430000877da961d8a6fc42870c6f3b40b6c0008712c4e73e94d9cb87417b13", + "aa14a7608706e75481ba40008801664f372978e80087b1a2bc2ec50000878e1bc9bf", + "040000880aa0f45e97dd488587d8883c83b23e5c88012feed34366c55a87517dea96", + "81efc9872386f26fc10000880213800b9cfa4000876a94d74f430000880103591cfc", + "9a800087e5266884034000880544408bdfbb3a318768b22c1647880087d5c2fee17e", + "4400880138a388a43c00008805bd904cb9f9700087470de4df820000880267b6bcdf", + "afca00876a94d74f430000873a9ea99ecb400088016345785d8a0000879d6298db74", + "f8008801e3ebe71ee3ab34870c6f3b40b6c00087519abcf34a3166870405a647881a", + "318703d7b8fd6c74498703d89d2ac313418703d89d2ac313418703ddd9bc9b7dc187", + "03e0e52848ba998703d89d2ac31341880cd2c8c6c7d254328703e558780a19b18802", + "ea11e32ad500008803782dace9d900008711c37937e0800087f46d3c488c00008725", + "5e5c51db83888802c68af0bb14000088017508f1956a800087167351fc47b6128801", + "fc09be0ae180008718017f965c6bc1872386f26fc10000870650de2e292bc9880b31", + "f1f2025c1c008722e6e03e460000880890f2095aa214c888081b0d335d920cc888c9", + "1ca9f56a2e2ccf873b4fa1acebc53f840260c0e48402738f57835387a08801634578", + "5d8a000083546f2e87feac8705a88800878e1bc9bf040000880142805d2280a00083", + "fb5b1e88016345785d8a000083fb5b1e87a78df2b8f869e38801ca34ce4d67400088", + "046bff6328235000870e0f7720d05c0087470de4df820000876a94d74f4300008805", + "2345c117835400880105fe1f7614d80087b1a2bc2ec5000087b73b24ce0efc00882a", + "9e9cd3f07ccc008801b48822206b1400871bfc55d8265c008726ca6fa6957c0087da", + "315ce6b078008758d15e176280008764bdee90c8a400878e1bc9bf040000876c170f", + "ee1b923f87bccbbb1567080087574b32512af9ec87cadf032b4e4c008801c8be7535", + "9d200087463a37de7b9800871acd25ca14767c88016345785d8a0000880214e8348c", + "4f00008802c68af0bb140000881f399b1438a10000870419e3a92313e98791a94863", + "ca80008758d15e1762800088029be90101c600008702dbddc32b39f5871550f7dca7", + "000087753d533d96800087b1a2bc2ec500008703e24324884d11880ca485ac5e6180", + "0088010a741a4627800087b1a2bc2ec5000087027ca57357c00087305120c0f20000", + "8709e217b4f92457876a94d74f43000087047aef9353b0f18779ecfc3cc27fba871e", + "8c57c526979488011c37937e0800008806fc8a412fd9b4c888080772fd2059abcb88", + "06ef2575a90c14c887022eb88560e000871f9e80ba80400087b1a2bc2ec500008769", + "fd794e3b5400876540995819400088058d15e176280000870151a2dd0a8e58868cc8", + "cbbd2718870120997d5c74d8870122f15b5e5068880254eb6ac98be40088060428ee", + "a0726cf288025136d946724c8687134ca2f322d47b870c0443c94738ca880230cf29", + "a6cead63882b05699353b60000821e068802c68af0bb140000865af3107a40008804", + "e7cc54b782e00082069887b1a2bc2ec500008803d1792daafd379e880a6587720ee4", + "f8a08803d0e1e204bc58588801a9875ef15fe2ce8721a12b68dcc48087123e61743c", + "40c487272c3bee09600087038d7ea4c68000877e7a02ea010000880c58adeab6699c", + "008801c9a85eec341c00880dd68163f03b832888018a8788fdde4c8b8814d1120d7b", + "1600008809f80ff8927e54a4879009abfad5bdec87470de4df82000087da3bfdcaeb", + "70ce872386f26fc10000888c2a687ce7720000884db7325476300000872386f26fc1", + "0000871be28ed71ea61d871aa535d3d0c00087470de4df820000870aa87bee538000", + "8736a5af0d195800880de0b6b3a764000087dfd22a8cd98000872bb2c8eabcc00088", + "1c7310237d8d000087e3c99a970ac5788801724634310ecffb87d529ae9e86000088", + "0221a0b8bb67309988011c9cb89693a4008708e1bc9bf0400087d7e2fddfac380087", + "59c3f6ccb0d32a874a340650678c00880429d069189e000088077551be7010671285", + "7e287bc0388803782dace9d90000858b66806e4b88016345785d8a00008644b7c685", + "58c687cfc02f26fbcd5087c70b69a22ba34888016345785d8a00008805b8bb3f167f", + "5de088030d98d59a96000088030d98d59a96000088030d98d59a96000087670758aa", + "7c800088016345785d8a000087c9149bde3f040087ae153d89fe800087b1a2bc2ec5", + "00008802c2478bf55900008727593313aca0008740c5faebeaa00087a1771fddd794", + "008801e17dda386040008773aa0911f28b2187e982c032b95c8a886f05b59d3b2000", + "0087710a97ecf320008726dad0ee3c8000880148a04289b94000873b3c35a991a0de", + "8809b6e64a8ec600008806f05b59d3b20000876a94d74f43000087470de4df820000", + "8802c68af0bb14000087353f531ef6f00088011c37937e0800008701117c811a71c2", + "88044d575b885f000087202f1ccd053000870aa87bee53800087532a5c898c100087", + "e69234c5ec40008901881158aa9f1996d7876d0864742820008708e1bc9bf0400087", + "0375c45e4f1f79878394b45cb5e8f98770409fc7a4ad69872386f26fc10000881c8b", + "ee99fefa80008829a2241af62c00008801ba46dd2d7f994f88026f0a70c8125c0088", + "01ca6ff5a82d73fd8803d4009fb8d838c888051c32a739651ae888086c5fe0a750c0", + "6f88042b283854af0c5687c6f3b40b6c000087f939b9f26e480087a3fb539e21f33f", + "88077e772392b6000082080d856f48b89340880429d069189e0000857295d0000088", + "0429d069189e000087b1a2bc2ec500008801b477fb95d6e19c82790488027cb44362", + "46e68888057edfe6e30e00008721143131bfa58087100eea826280008802c68af0bb", + "14000087eadca295d40bc08801b3fe4cb8c1d29e875d324adfee5c008808c15b5687", + "12040088023a3bcfb4a42800880214e8348c4f000088013fbe85edc900008801ca69", + "7a00dece5b871e96bfd78420008802c68af0bb140000881bc028b6c498b000881e42", + "0c942408f0008745b9be365ca000878e1bc9bf04000087b1a2bc2ec50000870ae966", + "4a255f3e874ab77dfbce88008806d1dfee575b867087b1a2bc2ec5000087cf529780", + "c6118e8801b477fb95d6e19c87470de4df8200008801793a25be4040008801251950", + "19f84000872386f26fc1000088016345785d8a0000870c6f3b40b6c0008801634578", + "5d8a00008722572c91ea4e5588010c8c1193e5681c880278d8bc980aae2d878e1bc9", + "bf040000890c4e42014d6dac00008806f05b59d3b20000878700cc7577000087498a", + "8a52d9c000871f9e80ba804000872386f26fc10000880de0b6b3a764000087ed24c5", + "76c1e0008804b0359c32a56c168801635985ec088cef880173b8b22fce489887470d", + "e4df82000087a3aeb1f502c4488802c68af0bb140000870221b262dd800089077dc3", + "683c72d02000880429d069189e00008824b4f6923a41d76888051787d7788186bb88", + "042a06676f2d9fa087a4f78ef76f5467880121ae7c4682c9c28609e6786b6bd38609", + "efd7a8dbab88021f49ef7e41e1278802c68af0bb140000880122f6e0a43fd07887ae", + "153d89fe80008806ef068e5c7092c087b1a2bc2ec500008807787efa8a6b84008711", + "270a58f9c000870645e0692fe0008802a40c017b5700f68758d15e176280008801cd", + "da4faccd0000872aa1efb94e0000879982da78799099872386f26fc1000088013fbe", + "85edc900008761b31ab352c00088029ec9346d9ef988872386f26fc10000880b19d0", + "cfdbd5c000880214e8348c4f000088013fbe85edc9000088058d15e1762800008802", + "5bf6196bd10000871ff973cafa80008716b51e037ffc00880429d069189e00008803", + "e2c284391c000087af9746059a110f87fbf47beb009960881b4e7d7cb0ea9d038747", + "0de4df8200008801b4736c92a8200087fc3e1fb30d800087c4a78a7ac3e6ad870c04", + "43c94738ca8801bc16d674ec800088113fa2d8e84ca9008775fd770992376e879fdf", + "42f6e4800088183ca64f3bd716738802fef288d0652a28872f5dd656123c488805a0", + "9087648aa1488703cb6e6915a800870a9fc0c1bc8000870a963408f75800870aa74d", + "4049b8008766f08a7177ad27832a9bb78806f05b59d3b20000832dd3868771671225", + "3b000087716712253b00008801f161421c8e0000880342e341423780008801634578", + "5d8a000088082bd67afbc0000088b72fd2103b2800008802ba7ea83775c2aa87817f", + "14f6102000870fd239236d6300878e1bc9bf04000087bd75a22d17cc008801b2e3f2", + "5e8834008801f161421c8e00008801f161421c8e000087da3186cf8680008805109a", + "fbcb1d562487b1a2bc2ec50000890340a45730235e000088029d80a15f843a208801", + "2eeecb52b54000868bab83b51190872976eb19c136b6876a94d74f430000876220ec", + "9ceea2fa880142427206494000880199f7496099700087dc7f78685be400877e936f", + "5c17ea668758d15e1762800087ef6c03195be00087894d3de2a27000890111fcc770", + "34d991e887b1a2bc2ec50000880853a0d2313c0000873d4318b6881e00870a4d88dd", + "d940008803ed0220305a78b6872386f26fc10000872386f26fc10000874436089976", + "0c6f88479c00a94104000087f195a3c4ba000087206697785a00008746bbd72822b6", + "118804db7325476300008809fd7af31b21f8008802c4383d6df3d000875bb3a1b5b0", + "34008766875a68d3bc0087f46164d412680088074211baa1878000880dda214bea69", + "c44487470de4df820000880de0b6b3a76400008801f1a4d8434cbf5f8701c6bf5263", + "4000872fd345a633ca6888010b5170d823dbcc831ef5fe88016345785d8a00008320", + "73ea825704821d048802b4e6142055c40088169a2f45f3e5ace0876a94d74f430000", + "87ce230518c88c0087adefff1fca640087ad345e2992cc00880168e909d1d1c80087", + "1d5ba7383e855d8711c37937e08000870e0cb529920b00870e0cb529920b00870e0c", + "b529920b008702e238aef418e087043c7f8ef8680087045dda08fe9b80870f1ba04f", + "e8480087077774b2768cc08713a0f54e62500087033fbb6c0a98c0870310d9e7e31a", + "a08702fafd0129ae408702baf467052bc0872714711487800088014b0fbe2ffd3a38", + "8802c68af0bb140000886124fee993bc000088010150a5573e8ee888035115c53aa7", + "811688c9310ff96afbd9a4877d4d8013bbf630876e7d490483c0008803782dace9d9", + "00008802c68af0bb140000880163e4cca7cb9e28887ce66c50e28400008718de7681", + "6d8000880f8b0a10e4700000873c6568f12e800088016345785d8a0000880c9d6aaf", + "14d4800087470de4df8200008804ba30aebb6aa90087110d9316ec0000880521304d", + "638b5cc088027f7d0bdb920000873fe9cf47826000870d00cdb9a6873c8814d1120d", + "7b16000087b1a2bc2ec5000087b1a2bc2ec500008803782dace9d900008721a36424", + "d7e388874aa41e8474883d8704f4706b9de057876a94d74f43000087114d3d3c0e60", + "008801cdda4faccd000087c2281d6449222f872ffd7455a04000879e3bfaf9ec9741", + ].join(""); + + const txValues: BigNumber[] = RLP.decode(rlpEncodedTxValueSamples).map( + (x) => BigNumber.from(x), + ); + + let pseudoFloatByteCount = 0; + let rlpByteCount = 0; + + for (const value of txValues) { + const encoded = encodePseudoFloat(value); + const [decoded] = await pseudoFloat.decodePublic(encoded); + expect(BigNumber.from(value).eq(decoded)).to.eq(true); + + const byteLen = (encoded.length - 2) / 2; + pseudoFloatByteCount += byteLen; + + const rlpEncoded = RLP.encode(BigNumber.from(value).toHexString()); + rlpByteCount += (rlpEncoded.length - 2) / 2; + } + + const avgBytesRlp = rlpByteCount / txValues.length; + expect(avgBytesRlp.toFixed(2)).to.eq("8.24"); + + const avgBytesPseudoFloat = pseudoFloatByteCount / txValues.length; + expect(avgBytesPseudoFloat.toFixed(2)).to.eq("4.40"); + // Almost twice as good. + // With economic incentive, most values could be 3 sig figs, which reduces + // further to just 2 bytes (>4x as good as RLP). + }); }); From 1d887e696ec27288f328dd37ea3eb3190e61f4ab Mon Sep 17 00:00:00 2001 From: Andrew Morris Date: Fri, 10 Feb 2023 17:35:33 +1100 Subject: [PATCH 5/5] Use pseudo-floats for ethValues and gas --- contracts/contracts/FallbackExpander.sol | 33 ++++++++++--------- contracts/shared/helpers/bundleCompression.ts | 4 +-- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/contracts/contracts/FallbackExpander.sol b/contracts/contracts/FallbackExpander.sol index 3fd49e7e..97edb022 100644 --- a/contracts/contracts/FallbackExpander.sol +++ b/contracts/contracts/FallbackExpander.sol @@ -3,6 +3,7 @@ pragma solidity >=0.7.0 <0.9.0; pragma abicoder v2; import "./lib/VLQ.sol"; +import "./lib/PseudoFloat.sol"; import "./interfaces/IExpander.sol"; import "./interfaces/IWallet.sol"; @@ -22,16 +23,16 @@ import "./interfaces/IWallet.sol"; * 24f1fc8a1f7256dc2914e524966309df2226fd329373aaaae1881bf5cd0c62f4 // BLS key * * 00 // nonce: 0 - * 868d20 // gas: 100,000 + * 3100 // gas: 100,000 * 02 // two actions * * // Action 1 - * 95ecd98ed5f38000 // ethValue: 12300000000000000 (0.0123 ETH) + * 7b0f // ethValue: 12300000000000000 (0.0123 ETH) * 70997970c51812dc3a010c7d01b50e0d17dc79c8 // contractAddress * 00 // encodedFunction: (empty) * * // Action 2 - * 82dd9fbdf38000 // ethValue: 12000000000000 (0.000012 ETH) + * 6c01 // ethValue: 12000000000000 (0.000012 ETH) * 4bd2e4e99b50a2a9e6b9dabfa3c8dcd1f885f008 // contractAddress (AggUtils) * 04 // 4 bytes for encodedFunction * 1dfea6a0 // sendEthToTxOrigin @@ -39,8 +40,8 @@ import "./interfaces/IWallet.sol"; * The proposal doc for the new expander lists the same example ("Example of an * Expanded User Operation" https://hackmd.io/0q7H3Ad0Su-I4RWWK8wQPA) using the * solidity ABI, which uses 608 bytes. Here we've encoded the same thing (plus - * gas) in 194 bytes, which is (about) 70% smaller. (If you account for the - * zero-byte discount, the saving is still over 30%.) + * gas) in 182 bytes, which is 70% smaller. (If you account for the zero-byte + * discount, the saving is still over 30%.) */ contract FallbackExpander is IExpander { function expand(bytes calldata stream) external pure returns ( @@ -49,30 +50,30 @@ contract FallbackExpander is IExpander { uint256 bytesRead ) { uint256 originalStreamLen = stream.length; - uint256 vlqValue; + uint256 decodedValue; senderPublicKey = abi.decode(stream[:128], (uint256[4])); stream = stream[128:]; - (vlqValue, stream) = VLQ.decode(stream); - operation.nonce = vlqValue; + (decodedValue, stream) = VLQ.decode(stream); + operation.nonce = decodedValue; - (vlqValue, stream) = VLQ.decode(stream); - operation.gas = vlqValue; + (decodedValue, stream) = PseudoFloat.decode(stream); + operation.gas = decodedValue; - (vlqValue, stream) = VLQ.decode(stream); - operation.actions = new IWallet.ActionData[](vlqValue); + (decodedValue, stream) = VLQ.decode(stream); + operation.actions = new IWallet.ActionData[](decodedValue); for (uint256 i = 0; i < operation.actions.length; i++) { uint256 ethValue; - (ethValue, stream) = VLQ.decode(stream); + (ethValue, stream) = PseudoFloat.decode(stream); address contractAddress = address(bytes20(stream[:20])); stream = stream[20:]; - (vlqValue, stream) = VLQ.decode(stream); - bytes memory encodedFunction = stream[:vlqValue]; - stream = stream[vlqValue:]; + (decodedValue, stream) = VLQ.decode(stream); + bytes memory encodedFunction = stream[:decodedValue]; + stream = stream[decodedValue:]; operation.actions[i] = IWallet.ActionData({ ethValue: ethValue, diff --git a/contracts/shared/helpers/bundleCompression.ts b/contracts/shared/helpers/bundleCompression.ts index b6f6a4d3..a4166838 100644 --- a/contracts/shared/helpers/bundleCompression.ts +++ b/contracts/shared/helpers/bundleCompression.ts @@ -26,12 +26,12 @@ export function compressAsFallback( ); result.push(encodeVLQ(operation.nonce)); - result.push(encodeVLQ(operation.gas)); + result.push(encodePseudoFloat(operation.gas)); result.push(encodeVLQ(operation.actions.length)); for (const action of operation.actions) { - result.push(encodeVLQ(action.ethValue)); + result.push(encodePseudoFloat(action.ethValue)); result.push(action.contractAddress); const fnHex = ethers.utils.hexlify(action.encodedFunction);