From f7737f5828d600face747060c046802e35800ff5 Mon Sep 17 00:00:00 2001 From: cristovaoth Date: Fri, 30 Jun 2023 12:57:13 +0100 Subject: [PATCH] Introducing the new AbiEncoded node. Expanding TypeEquivalent to include AbiEncoded (in addition to Calldata) --- packages/evm/contracts/Decoder.sol | 7 +- packages/evm/contracts/Integrity.sol | 13 +- packages/evm/contracts/Types.sol | 5 +- packages/evm/test/Integrity.spec.ts | 152 ++++++++++++++---- .../test/decoder/ParameterTypeDynamic.spec.ts | 63 ++++++++ packages/evm/test/utils.ts | 1 + 6 files changed, 202 insertions(+), 39 deletions(-) diff --git a/packages/evm/contracts/Decoder.sol b/packages/evm/contracts/Decoder.sol index 73cba1bd2..00df57bff 100644 --- a/packages/evm/contracts/Decoder.sol +++ b/packages/evm/contracts/Decoder.sol @@ -76,10 +76,13 @@ library Decoder { result ); result.size += 32; - } else if (paramType == ParameterType.Calldata) { + } else if ( + paramType == ParameterType.Calldata || + paramType == ParameterType.AbiEncoded + ) { __block__( data, - location + 32 + 4, + location + 32 + (paramType == ParameterType.Calldata ? 4 : 0), node, node.children.length, false, diff --git a/packages/evm/contracts/Integrity.sol b/packages/evm/contracts/Integrity.sol index ecaaba070..e061feb8c 100644 --- a/packages/evm/contracts/Integrity.sol +++ b/packages/evm/contracts/Integrity.sol @@ -63,7 +63,8 @@ library Integrity { if ( paramType != ParameterType.Tuple && paramType != ParameterType.Array && - paramType != ParameterType.Calldata + paramType != ParameterType.Calldata && + paramType != ParameterType.AbiEncoded ) { revert UnsuitableParameterType(index); } @@ -201,7 +202,8 @@ library Integrity { } } else if ( condition.paramType == ParameterType.Tuple || - condition.paramType == ParameterType.Calldata + condition.paramType == ParameterType.Calldata || + condition.paramType == ParameterType.AbiEncoded ) { if (childBounds.length == 0) { revert UnsuitableChildCount(i); @@ -286,9 +288,12 @@ library Integrity { uint256 j, Topology.Bounds[] memory childrenBounds ) private pure returns (bool) { + ParameterType leftParamType = Topology + .typeTree(conditions, i, childrenBounds) + .paramType; return - Topology.typeTree(conditions, i, childrenBounds).paramType == - ParameterType.Calldata && + (leftParamType == ParameterType.Calldata || + leftParamType == ParameterType.AbiEncoded) && Topology.typeTree(conditions, j, childrenBounds).paramType == ParameterType.Dynamic; } diff --git a/packages/evm/contracts/Types.sol b/packages/evm/contracts/Types.sol index 03f5ab935..ef4c3e387 100644 --- a/packages/evm/contracts/Types.sol +++ b/packages/evm/contracts/Types.sol @@ -13,7 +13,8 @@ enum ParameterType { Dynamic, Tuple, Array, - Calldata + Calldata, + AbiEncoded } enum Operator { @@ -33,7 +34,7 @@ enum Operator { /* 04: */ _Placeholder04, // ------------------------------------------------------------ // 05-14: COMPLEX EXPRESSIONS - // paramType: Calldata / Tuple / Array, + // paramType: Calldata / AbiEncoded / Tuple / Array, // ✅ children // 🚫 compValue /* 05: */ Matches, diff --git a/packages/evm/test/Integrity.spec.ts b/packages/evm/test/Integrity.spec.ts index 58a9fe13c..f79065e31 100644 --- a/packages/evm/test/Integrity.spec.ts +++ b/packages/evm/test/Integrity.spec.ts @@ -1568,49 +1568,120 @@ describe("Integrity", async () => { it("sibling type equivalence - ok", async () => { const { integrity, enforce } = await loadFixture(setup); - const conditions = [ - { - parent: 0, - paramType: ParameterType.None, - operator: Operator.Or, - compValue: "0x", - }, - { - parent: 0, - paramType: ParameterType.Calldata, - operator: Operator.Pass, - compValue: "0x", - }, - { - parent: 0, - paramType: ParameterType.Dynamic, - operator: Operator.Pass, - compValue: "0x", - }, - { - parent: 1, - paramType: ParameterType.Static, - operator: Operator.Pass, - compValue: "0x", - }, - ]; - await expect(enforce(conditions)).to.not.be.reverted; + // A function with a dynamic argument, which is also an embedded Calldata encoded field + await expect( + enforce([ + { + parent: 0, + paramType: ParameterType.Calldata, + operator: Operator.Pass, + compValue: "0x", + }, + { + parent: 0, + paramType: ParameterType.None, + operator: Operator.And, + compValue: "0x", + }, + { + parent: 1, + paramType: ParameterType.Calldata, + operator: Operator.Pass, + compValue: "0x", + }, + { + parent: 1, + paramType: ParameterType.Dynamic, + operator: Operator.Pass, + compValue: "0x", + }, + { + parent: 2, + paramType: ParameterType.Static, + operator: Operator.Pass, + compValue: "0x", + }, + ]) + ).to.not.be.reverted; - // Dynamic should not come first for EquvialentSiblingTypeTrees + // A function with a dynamic argument, which is also an embedded AbiEncoded encoded field await expect( enforce([ + { + parent: 0, + paramType: ParameterType.Calldata, + operator: Operator.Pass, + compValue: "0x", + }, { parent: 0, paramType: ParameterType.None, - operator: Operator.Or, + operator: Operator.And, compValue: "0x", }, + { + parent: 1, + paramType: ParameterType.AbiEncoded, + operator: Operator.Pass, + compValue: "0x", + }, + { + parent: 1, + paramType: ParameterType.Dynamic, + operator: Operator.Pass, + compValue: "0x", + }, + { + parent: 2, + paramType: ParameterType.Static, + operator: Operator.Pass, + compValue: "0x", + }, + ]) + ).to.not.be.reverted; + + // Dynamic can't come before the Calldata node that actually defines the type tree and should be the Anchor + await expect( + enforce([ { parent: 0, + paramType: ParameterType.Calldata, + operator: Operator.Pass, + compValue: "0x", + }, + { + parent: 0, + paramType: ParameterType.None, + operator: Operator.And, + compValue: "0x", + }, + { + parent: 1, paramType: ParameterType.Dynamic, operator: Operator.Pass, compValue: "0x", }, + { + parent: 1, + paramType: ParameterType.Calldata, + operator: Operator.Pass, + compValue: "0x", + }, + + { + parent: 3, + paramType: ParameterType.Static, + operator: Operator.Pass, + compValue: "0x", + }, + ]) + ) + .to.be.revertedWithCustomError(integrity, "UnsuitableChildTypeTree") + .withArgs(1); + + // Dynamic can't come before the AbiEncoded node that actually defines the type tree and should be the Anchor + await expect( + enforce([ { parent: 0, paramType: ParameterType.Calldata, @@ -1618,7 +1689,26 @@ describe("Integrity", async () => { compValue: "0x", }, { - parent: 2, + parent: 0, + paramType: ParameterType.None, + operator: Operator.And, + compValue: "0x", + }, + { + parent: 1, + paramType: ParameterType.Dynamic, + operator: Operator.Pass, + compValue: "0x", + }, + { + parent: 1, + paramType: ParameterType.Calldata, + operator: Operator.Pass, + compValue: "0x", + }, + + { + parent: 3, paramType: ParameterType.Static, operator: Operator.Pass, compValue: "0x", @@ -1626,7 +1716,7 @@ describe("Integrity", async () => { ]) ) .to.be.revertedWithCustomError(integrity, "UnsuitableChildTypeTree") - .withArgs(0); + .withArgs(1); }); }); }); diff --git a/packages/evm/test/decoder/ParameterTypeDynamic.spec.ts b/packages/evm/test/decoder/ParameterTypeDynamic.spec.ts index baeb76972..faca60de5 100644 --- a/packages/evm/test/decoder/ParameterTypeDynamic.spec.ts +++ b/packages/evm/test/decoder/ParameterTypeDynamic.spec.ts @@ -218,6 +218,69 @@ describe("Decoder library", async () => { await decoder.pluck(data as string, tupleField.location, tupleField.size) ).to.equal(encode(["bytes"], ["0xbadfed"], YesRemoveOffset)); }); + it("plucks Dynamic from embedded AbiEncoded", async () => { + const { decoder } = await loadFixture(setup); + + const iface = new Interface(["function fn(bytes a)"]); + const embedded = defaultAbiCoder.encode( + ["bytes", "bool", "bytes2[]"], + ["0xbadfed", true, ["0xccdd", "0x3333"]] + ); + + const data = iface.encodeFunctionData("fn", [embedded]); + + const condition = { + paramType: ParameterType.Calldata, + operator: Operator.Matches, + children: [ + { + paramType: ParameterType.AbiEncoded, + operator: Operator.Matches, + children: [ + { + paramType: ParameterType.Dynamic, + operator: Operator.Pass, + children: [], + }, + { + paramType: ParameterType.Static, + operator: Operator.Pass, + children: [], + }, + { + paramType: ParameterType.Array, + operator: Operator.Pass, + children: [ + { + paramType: ParameterType.Static, + operator: Operator.Pass, + children: [], + }, + ], + }, + ], + }, + ], + }; + + const result = await decoder.inspect(data as string, condition); + + const field1 = result.children[0].children[0]; + expect( + await decoder.pluck(data as string, field1.location, field1.size) + ).to.equal(encode(["bytes"], ["0xbadfed"])); + + const field2 = result.children[0].children[1]; + expect( + await decoder.pluck(data as string, field2.location, field2.size) + ).to.equal(encode(["bool"], ["false"], false)); + + const field3 = result.children[0].children[2]; + expect( + await decoder.pluck(data as string, field3.location, field3.size) + ).to.equal(encode(["bytes2[]"], [["0xccdd", "0x3333"]])); + }); + it("plucks Dynamic from type equivalent branch", async () => { const { decoder } = await loadFixture(setup); diff --git a/packages/evm/test/utils.ts b/packages/evm/test/utils.ts index a0eaf0233..85529e414 100644 --- a/packages/evm/test/utils.ts +++ b/packages/evm/test/utils.ts @@ -44,6 +44,7 @@ export enum ParameterType { Tuple, Array, Calldata, + AbiEncoded, } export enum Operator {