diff --git a/package-lock.json b/package-lock.json index 9978fcdc9..255ebbfb6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37632,6 +37632,7 @@ "json-refs": "3.0.15", "memorystore": "1.6.7", "pg": "8.12.0", + "semver": "7.6.2", "serve-index": "1.9.1", "solc": "0.8.26", "swagger-ui-express": "5.0.1", diff --git a/packages/lib-sourcify/src/lib/CheckedContract.ts b/packages/lib-sourcify/src/lib/CheckedContract.ts index 11ed5bb22..a1bdfcb35 100644 --- a/packages/lib-sourcify/src/lib/CheckedContract.ts +++ b/packages/lib-sourcify/src/lib/CheckedContract.ts @@ -478,11 +478,10 @@ export class CheckedContract { metadata: this.metadataRaw, // Sometimes the compiler returns empty object (not falsey). Convert it to undefined (falsey). immutableReferences: - contract.evm?.deployedBytecode?.immutableReferences && - Object.keys(contract.evm.deployedBytecode.immutableReferences).length > - 0 - ? contract.evm.deployedBytecode.immutableReferences - : {}, + contract.evm?.deployedBytecode?.immutableReferences || {}, + creationLinkReferences: contract?.evm?.bytecode?.linkReferences || {}, + runtimeLinkReferences: + contract?.evm?.deployedBytecode?.linkReferences || {}, }; } diff --git a/packages/lib-sourcify/src/lib/types.ts b/packages/lib-sourcify/src/lib/types.ts index 0c78d26e7..b40de274a 100644 --- a/packages/lib-sourcify/src/lib/types.ts +++ b/packages/lib-sourcify/src/lib/types.ts @@ -153,11 +153,25 @@ export interface ImmutableReferences { start: number; }>; } + +export interface LinkReferences { + [filePath: string]: { + [libraryName: string]: [ + { + length: number; + start: number; + }, + ]; + }; +} + export interface RecompilationResult { creationBytecode: string; runtimeBytecode: string; metadata: string; immutableReferences: ImmutableReferences; + creationLinkReferences: LinkReferences; + runtimeLinkReferences: LinkReferences; } export type Transformation = { @@ -176,7 +190,7 @@ export type Transformation = { export const CallProtectionTransformation = (): Transformation => ({ type: 'replace', reason: 'callProtection', - offset: 0, + offset: 1, // 1 byte is always the PUSH20 opcode 0x73 }); // TransformationValues only has one ConstructorTransformatino so no id field is needed diff --git a/packages/lib-sourcify/src/lib/verification.ts b/packages/lib-sourcify/src/lib/verification.ts index e36de36a0..362676ab1 100644 --- a/packages/lib-sourcify/src/lib/verification.ts +++ b/packages/lib-sourcify/src/lib/verification.ts @@ -14,12 +14,18 @@ import { CallProtectionTransformation, TransformationValues, CompiledContractCborAuxdata, + LinkReferences, } from './types'; import { decode as bytecodeDecode, splitAuxdata, } from '@ethereum-sourcify/bytecode-utils'; -import { getAddress, getCreateAddress, keccak256 } from 'ethers'; +import { + getAddress, + getCreateAddress, + keccak256, + id as keccak256Str, +} from 'ethers'; import { hexZeroPad, isHexString } from '@ethersproject/bytes'; import { BigNumber } from '@ethersproject/bignumber'; import { defaultAbiCoder as abiCoder, ParamType } from '@ethersproject/abi'; @@ -114,6 +120,7 @@ export async function verifyDeployed( runtimeBytecode, generateRuntimeCborAuxdataPositions, recompiled.immutableReferences, + recompiled.runtimeLinkReferences, ); if (match.runtimeMatch === 'partial') { logDebug('Matched with deployed bytecode', { @@ -132,6 +139,7 @@ export async function verifyDeployed( runtimeBytecode, generateRuntimeCborAuxdataPositions, recompiled.immutableReferences, + recompiled.runtimeLinkReferences, ); }, 'runtimeMatch', @@ -169,6 +177,7 @@ export async function verifyDeployed( creatorTxHash, recompiledMetadata, generateCreationCborAuxdataPositions, + recompiled.creationLinkReferences, ); if (match.runtimeMatch === 'partial') { logDebug('Matched partial with creation tx', { @@ -190,6 +199,7 @@ export async function verifyDeployed( creatorTxHash, recompiledMetadata, generateCreationCborAuxdataPositions, + recompiled.creationLinkReferences, ); }, 'creationMatch', @@ -413,7 +423,8 @@ export async function matchWithRuntimeBytecode( recompiledRuntimeBytecode: string, onchainRuntimeBytecode: string, generateCborAuxdataPositions: () => Promise, - immutableReferences?: ImmutableReferences, + immutableReferences: ImmutableReferences, + linkReferences: LinkReferences, ) { // Updating the `match.onchainRuntimeBytecode` here so we are sure to always update it match.onchainRuntimeBytecode = onchainRuntimeBytecode; @@ -436,30 +447,21 @@ export async function matchWithRuntimeBytecode( ); // Replace the library placeholders in the recompiled bytecode with values from the deployed bytecode - const { replaced, libraryMap } = addLibraryAddresses( + const { replaced, libraryMap } = handleLibraries( recompiledRuntimeBytecode, onchainRuntimeBytecode, + linkReferences, match.runtimeTransformations, + match.runtimeTransformationValues, ); recompiledRuntimeBytecode = replaced; - if (Object.keys(libraryMap).length > 0) { - // Prepend the library addresses with "0x", this is the format for the DB. FS library-map is without "0x" - match.runtimeTransformationValues.libraries = Object.keys( - libraryMap, - ).reduce((libMap: any, lib) => { - libMap[lib] = `0x${libraryMap[lib]}`; - return libMap; - }, {}); - } - if (immutableReferences) { - onchainRuntimeBytecode = replaceImmutableReferences( - immutableReferences, - onchainRuntimeBytecode, - match.runtimeTransformations, - match.runtimeTransformationValues, - ); - } + onchainRuntimeBytecode = replaceImmutableReferences( + immutableReferences, + onchainRuntimeBytecode, + match.runtimeTransformations, + match.runtimeTransformationValues, + ); // We call generateCborAuxdataPositions before returning because we always need // to fill cborAuxdata in creation_code_artifacts and runtime_code_artifacts @@ -525,6 +527,7 @@ export async function matchWithCreationTx( creatorTxHash: string, recompiledMetadata: Metadata, generateCborAuxdataPositions: () => Promise, + linkReferences: LinkReferences, ) { if (recompiledCreationBytecode === '0x') { match.creationMatch = null; @@ -569,24 +572,17 @@ export async function matchWithCreationTx( match.creationTransformationValues = {}; } - // The reason why this uses `startsWith` instead of `===` is that creationTxData may contain constructor arguments at the end part. // Replace the library placeholders in the recompiled bytecode with values from the deployed bytecode - const { replaced, libraryMap } = addLibraryAddresses( + const { replaced, libraryMap } = handleLibraries( recompiledCreationBytecode, match.onchainCreationBytecode, + linkReferences, match.creationTransformations, + match.creationTransformationValues, ); recompiledCreationBytecode = replaced; - if (Object.keys(libraryMap).length > 0) { - // Prepend the library addresses with "0x", this is the format for the DB. FS library-map is without "0x" - match.creationTransformationValues.libraries = Object.keys( - libraryMap, - ).reduce((libMap: any, lib) => { - libMap[lib] = `0x${libraryMap[lib]}`; - return libMap; - }, {}); - } + // The reason why this uses `startsWith` instead of `===` is that creationTxData may contain constructor arguments at the end part. if (match.onchainCreationBytecode.startsWith(recompiledCreationBytecode)) { // if the bytecode doesn't end with metadata then "partial" match if (endsWithMetadataHash(recompiledCreationBytecode)) { @@ -702,34 +698,62 @@ export async function matchWithCreationTx( } } -export function addLibraryAddresses( +export function handleLibraries( template: string, real: string, + linkReferences: LinkReferences, transformationsArray: Transformation[], + transformationValues: TransformationValues, ): { replaced: string; libraryMap: StringMap; } { - const PLACEHOLDER_START = '__'; - const PLACEHOLDER_LENGTH = 40; - const libraryMap: StringMap = {}; + for (const file in linkReferences) { + for (const lib in linkReferences[file]) { + for (const linkRefObj of linkReferences[file][lib]) { + const fqn = `${file}:${lib}`; // Fully Qualified (FQ) name + + const { start, length } = linkRefObj; + const strStart = start * 2 + 2; // Each byte 2 chars and +2 for 0x + const strLength = length * 2; + const placeholder = template.slice(strStart, strStart + strLength); + + // slice(2) removes 0x + const calculatedPlaceholder = + '__$' + keccak256Str(fqn).slice(2).slice(0, 34) + '$__'; + // Placeholder format was different pre v0.5.0 https://docs.soliditylang.org/en/v0.4.26/contracts.html#libraries + const trimmedFQN = fqn.slice(0, 36); // in case the fqn is too long + const calculatedPreV050Placeholder = '__' + trimmedFQN.padEnd(38, '_'); + + if ( + !( + placeholder === calculatedPlaceholder || + placeholder === calculatedPreV050Placeholder + ) + ) + throw new Error( + `Library placeholder mismatch: ${placeholder} vs ${calculatedPlaceholder} or ${calculatedPreV050Placeholder}`, + ); - let index = template.indexOf(PLACEHOLDER_START); - while (index !== -1) { - const placeholder = template.slice(index, index + PLACEHOLDER_LENGTH); - const address = real.slice(index, index + PLACEHOLDER_LENGTH); - libraryMap[placeholder] = address; + const address = real.slice(strStart, strStart + strLength); + libraryMap[placeholder] = address; - // Replace regex with simple string replacement - template = template.split(placeholder).join(address); + // Replace the specific occurrence of the placeholder + template = + template.slice(0, strStart) + + address + + template.slice(strStart + strLength); - transformationsArray.push( - // we divide by 2 because we store the length in bytes (without 0x) - LibraryTransformation((index - 2) / 2, placeholder), - ); + transformationsArray.push(LibraryTransformation(start, fqn)); - index = template.indexOf(PLACEHOLDER_START); + if (!transformationValues.libraries) { + transformationValues.libraries = {}; + } + // Prepend the library addresses with "0x", this is the format for the DB. FS library-map is without "0x" + transformationValues.libraries[fqn] = '0x' + address; + } + } } return { @@ -750,8 +774,9 @@ export function checkCallProtectionAndReplaceAddress( if (template.startsWith(callProtection)) { const replacedCallProtection = real.slice(0, 0 + callProtection.length); + const callProtectionAddress = replacedCallProtection.slice(4); // remove 0x73 transformationsArray.push(CallProtectionTransformation()); - transformationValues.callProtection = replacedCallProtection; + transformationValues.callProtection = '0x' + callProtectionAddress; return replacedCallProtection + template.substring(callProtection.length); } diff --git a/packages/lib-sourcify/test/verification.spec.ts b/packages/lib-sourcify/test/verification.spec.ts index ea4c1cf48..1078e0d38 100644 --- a/packages/lib-sourcify/test/verification.spec.ts +++ b/packages/lib-sourcify/test/verification.spec.ts @@ -711,6 +711,7 @@ describe('lib-sourcify tests', () => { wrongCreatorTxHash, recompiledMetadata, generateCreationCborAuxdataPositions, + recompiled.creationLinkReferences, ); } catch (err) { if (err instanceof Error) { @@ -772,6 +773,7 @@ describe('lib-sourcify tests', () => { txHash, recompiledMetadata, generateCreationCborAuxdataPositions, + recompiled.creationLinkReferences, ); expectMatch(match, null, contractAddress, undefined); // status is null }); @@ -817,6 +819,7 @@ describe('lib-sourcify tests', () => { creatorTxHash, recompiledMetadata, generateCreationCborAuxdataPositions, + recompiled.creationLinkReferences, ); expectMatch(match, null, contractAddress, undefined); // status is null }); @@ -854,6 +857,7 @@ describe('lib-sourcify tests', () => { creatorTxHash, recompiledMetadata, generateCreationCborAuxdataPositions, + recompiled.creationLinkReferences, ); try { expect(match.creationMatch).to.equal('perfect'); @@ -897,6 +901,7 @@ describe('lib-sourcify tests', () => { creatorTxHash, recompiledMetadata, generateCreationCborAuxdataPositions, + recompiled.creationLinkReferences, ); try { expect(match.creationMatch).to.equal('partial'); diff --git a/services/server/package.json b/services/server/package.json index b10d5ef66..f4272a305 100644 --- a/services/server/package.json +++ b/services/server/package.json @@ -70,6 +70,7 @@ "json-refs": "3.0.15", "memorystore": "1.6.7", "pg": "8.12.0", + "semver": "7.6.2", "serve-index": "1.9.1", "solc": "0.8.26", "swagger-ui-express": "5.0.1", diff --git a/services/server/src/server/services/storageServices/SourcifyDatabaseService.ts b/services/server/src/server/services/storageServices/SourcifyDatabaseService.ts index a485b9925..d9c4a8c2c 100644 --- a/services/server/src/server/services/storageServices/SourcifyDatabaseService.ts +++ b/services/server/src/server/services/storageServices/SourcifyDatabaseService.ts @@ -2,6 +2,7 @@ import { Match, CheckedContract, Status, + StringMap, } from "@ethereum-sourcify/lib-sourcify"; import logger from "../../../common/logger"; import * as Database from "../utils/database-util"; @@ -22,11 +23,12 @@ import { PaginatedContractData, } from "../../types"; import config from "config"; -import Path from "path"; +import Path, { format } from "path"; import { getFileRelativePath } from "../utils/util"; -import { getAddress } from "ethers"; +import { getAddress, id as keccak256Str } from "ethers"; import { BadRequestError } from "../../../common/errors"; import { RWStorageIdentifiers } from "./identifiers"; +import semver from "semver"; const MAX_RETURNED_CONTRACTS_BY_GETCONTRACTS = 200; @@ -307,9 +309,25 @@ export class SourcifyDatabaseService sourcifyMatch?.runtime_values?.libraries && Object.keys(sourcifyMatch.runtime_values.libraries).length > 0 ) { - files["library-map.json"] = JSON.stringify( + // Must convert "contracts/file.sol:MyLib" FQN format to the placeholder format __$keccak256(file.sol:MyLib)$___ or __MyLib__________ + const formattedLibraries: StringMap = {}; + for (const [key, value] of Object.entries( sourcifyMatch.runtime_values.libraries, - ); + )) { + let formattedKey; + // Solidity >= 0.5.0 is __$keccak256(file.sol:MyLib)$__ (total 40 characters) + if (semver.gte(sourcifyMatch.metadata.compiler.version, "0.5.0")) { + formattedKey = + "__$" + keccak256Str(key).slice(2).slice(0, 34) + "$__"; + } else { + // Solidity < 0.5.0 is __MyLib__________ (total 40 characters) + const libName = key.split(":")[1]; + const trimmedLibName = libName.slice(0, 36); // in case it's longer + formattedKey = "__" + trimmedLibName.padEnd(38, "_"); + } + formattedLibraries[formattedKey] = value; + } + files["library-map.json"] = JSON.stringify(formattedLibraries); } if ( diff --git a/services/server/test/helpers/assertions.ts b/services/server/test/helpers/assertions.ts index 7f1d032d9..e317f28ed 100644 --- a/services/server/test/helpers/assertions.ts +++ b/services/server/test/helpers/assertions.ts @@ -8,6 +8,10 @@ import { getMatchStatus } from "../../src/server/common"; import type { Response } from "superagent"; import type { Done } from "mocha"; import { Pool } from "pg"; +import { + Transformation, + TransformationValues, +} from "@ethereum-sourcify/lib-sourcify"; export const assertValidationError = ( err: Error | null, @@ -108,6 +112,55 @@ export const assertVerificationSession = async ( } }; +export async function assertTransformations( + sourcifyDatabase: Pool | null, + expectedAddress: string | undefined, + expectedChain: string | undefined, + expectedRuntimeTransformations: Transformation[] | null, + expectedRuntimeTransformationValues: TransformationValues | null, + expectedCreationTransformations: Transformation[] | null, + expectedCreationTransformationValues: TransformationValues | null, +) { + if (sourcifyDatabase) { + // Check if saved to the database + const res = await sourcifyDatabase.query( + `SELECT + cd.address, + cd.chain_id, + vc.runtime_transformations, + vc.runtime_values, + vc.creation_transformations, + vc.creation_values + FROM sourcify_matches sm + LEFT JOIN verified_contracts vc ON vc.id = sm.verified_contract_id + LEFT JOIN contract_deployments cd ON cd.id = vc.deployment_id + WHERE cd.address = $1 AND cd.chain_id = $2`, + [Buffer.from(expectedAddress?.substring(2) ?? "", "hex"), expectedChain], + ); + + const contract = res.rows[0]; + chai.expect(contract).to.not.be.null; + + chai + .expect("0x" + contract.address.toString("hex")) + .to.equal(expectedAddress?.toLowerCase()); + chai.expect(contract.chain_id).to.equal(expectedChain); + + chai + .expect(contract.runtime_transformations) + .to.deep.equal(expectedRuntimeTransformations); + chai + .expect(contract.runtime_values) + .to.deep.equal(expectedRuntimeTransformationValues); + chai + .expect(contract.creation_transformations) + .to.deep.equal(expectedCreationTransformations); + chai + .expect(contract.creation_values) + .to.deep.equal(expectedCreationTransformationValues); + } +} + async function assertContractSaved( sourcifyDatabase: Pool | null, expectedAddress: string | undefined, diff --git a/services/server/test/integration/verification-handlers/verify.stateless.spec.ts b/services/server/test/integration/verification-handlers/verify.stateless.spec.ts index 7efd90a22..7051ac125 100644 --- a/services/server/test/integration/verification-handlers/verify.stateless.spec.ts +++ b/services/server/test/integration/verification-handlers/verify.stateless.spec.ts @@ -1,4 +1,5 @@ import { + assertTransformations, assertValidationError, assertVerification, } from "../../helpers/assertions"; @@ -17,6 +18,10 @@ import { deployFromAbiAndBytecode, } from "../../helpers/helpers"; import hardhatOutputJSON from "../../sources/hardhat-output/output.json"; +import { + CallProtectionTransformation, + LibraryTransformation, +} from "@ethereum-sourcify/lib-sourcify"; chai.use(chaiHttp); @@ -762,6 +767,72 @@ describe("/", function () { }); }); }); + it("should verify a contract compiled with Solidity < 0.5.0 with non-keccak values for library placeholders", async () => { + const artifact = ( + await import("../../testcontracts/LibrariesPreSolidity050/artifact.json") + ).default; + const address = await deployFromAbiAndBytecode( + chainFixture.localSigner, + artifact.abi, + artifact.bytecode, + ); + const metadata = ( + await import("../../testcontracts/LibrariesPreSolidity050/metadata.json") + ).default; + + const file = fs.readFileSync( + path.join( + __dirname, + "..", + "..", + "testcontracts", + "LibrariesPreSolidity050", + "sources", + "ClaimHolderLibrary.sol", + ), + ); + + const res = await chai + .request(serverFixture.server.app) + .post("/") + .field("address", address) + .field("chain", chainFixture.chainId) + .attach("files", Buffer.from(JSON.stringify(metadata)), "metadata.json") + .attach("files", file, "ClaimHolderLibrary.sol"); + + await assertVerification( + serverFixture.sourcifyDatabase, + null, + res, + null, + address, + chainFixture.chainId, + "perfect", + ); + + const libraryFQN = "ClaimHolderLibrary.sol:KeyHolderLibrary"; + const libraryAddress = "0xcafecafecafecafecafecafecafecafecafecafe"; + + await assertTransformations( + serverFixture.sourcifyDatabase, + address, + chainFixture.chainId, + [ + CallProtectionTransformation(), + LibraryTransformation(1341, libraryFQN), + LibraryTransformation(3043, libraryFQN), + LibraryTransformation(3262, libraryFQN), + ], + { + libraries: { + [libraryFQN]: libraryAddress, + }, + callProtection: address.toLowerCase(), // call protection works by PUSH20ing contract's own address. Bytecode chars are all lowercase but address is mixed due to checksumming + }, + null, + null, + ); + }); it("should verify a contract compiled with Solidity < 0.7.5 and libraries have been linked using compiler settings", async () => { const artifact = ( diff --git a/services/server/test/testcontracts/LibrariesPreSolidity050/artifact.json b/services/server/test/testcontracts/LibrariesPreSolidity050/artifact.json new file mode 100644 index 000000000..7e29e3e31 --- /dev/null +++ b/services/server/test/testcontracts/LibrariesPreSolidity050/artifact.json @@ -0,0 +1,244 @@ +{ + "abi": [ + { + "constant": false, + "inputs": [ + { + "name": "_keyHolderData", + "type": "KeyHolderLibrary.KeyHolderData storage" + }, + { + "name": "_claims", + "type": "ClaimHolderLibrary.Claims storage" + }, + { + "name": "_topic", + "type": "uint256[]" + }, + { + "name": "_issuer", + "type": "address[]" + }, + { + "name": "_signature", + "type": "bytes" + }, + { + "name": "_data", + "type": "bytes" + }, + { + "name": "_offsets", + "type": "uint256[]" + } + ], + "name": "addClaims", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_keyHolderData", + "type": "KeyHolderLibrary.KeyHolderData storage" + }, + { + "name": "_claims", + "type": "ClaimHolderLibrary.Claims storage" + }, + { + "name": "_claimId", + "type": "bytes32" + } + ], + "name": "removeClaim", + "outputs": [ + { + "name": "success", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_claims", + "type": "ClaimHolderLibrary.Claims storage" + }, + { + "name": "_claimId", + "type": "bytes32" + } + ], + "name": "getClaim", + "outputs": [ + { + "name": "topic", + "type": "uint256" + }, + { + "name": "scheme", + "type": "uint256" + }, + { + "name": "issuer", + "type": "address" + }, + { + "name": "signature", + "type": "bytes" + }, + { + "name": "data", + "type": "bytes" + }, + { + "name": "uri", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_keyHolderData", + "type": "KeyHolderLibrary.KeyHolderData storage" + }, + { + "name": "_claims", + "type": "ClaimHolderLibrary.Claims storage" + }, + { + "name": "_topic", + "type": "uint256" + }, + { + "name": "_scheme", + "type": "uint256" + }, + { + "name": "_issuer", + "type": "address" + }, + { + "name": "_signature", + "type": "bytes" + }, + { + "name": "_data", + "type": "bytes" + }, + { + "name": "_uri", + "type": "string" + } + ], + "name": "addClaim", + "outputs": [ + { + "name": "claimRequestId", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "claimId", + "type": "bytes32" + }, + { + "indexed": true, + "name": "topic", + "type": "uint256" + }, + { + "indexed": false, + "name": "scheme", + "type": "uint256" + }, + { + "indexed": true, + "name": "issuer", + "type": "address" + }, + { + "indexed": false, + "name": "signature", + "type": "bytes" + }, + { + "indexed": false, + "name": "data", + "type": "bytes" + }, + { + "indexed": false, + "name": "uri", + "type": "string" + } + ], + "name": "ClaimAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "claimId", + "type": "bytes32" + }, + { + "indexed": true, + "name": "topic", + "type": "uint256" + }, + { + "indexed": false, + "name": "scheme", + "type": "uint256" + }, + { + "indexed": true, + "name": "issuer", + "type": "address" + }, + { + "indexed": false, + "name": "signature", + "type": "bytes" + }, + { + "indexed": false, + "name": "data", + "type": "bytes" + }, + { + "indexed": false, + "name": "uri", + "type": "string" + } + ], + "name": "ClaimRemoved", + "type": "event" + } + ], + "bytecode": "0x611302610030600b82828239805160001a6073146000811461002057610022565bfe5b5030600052607381538281f300730000000000000000000000000000000000000000301460806040526004361061005f5763ffffffff60e060020a600035041663428e59ee81146100645780635c02fae1146101b25780638522d0eb146101e4578063bd2d0c281461035d575b600080fd5b81801561007057600080fd5b5060408051602060046044358181013583810280860185019096528085526101b0958335956024803596369695606495939492019291829185019084908082843750506040805187358901803560208181028481018201909552818452989b9a99890198929750908201955093508392508501908490808284375050604080516020601f89358b018035918201839004830284018301909452808352979a99988101979196509182019450925082915084018382808284375050604080516020601f89358b018035918201839004830284018301909452808352979a999881019791965091820194509250829150840183828082843750506040805187358901803560208181028481018201909552818452989b9a9989019892975090820195509350839250850190849080828437509497506104609650505050505050565b005b8180156101be57600080fd5b506101d0600435602435604435610532565b604080519115158252519081900360200190f35b6101f260043560243561094a565b6040518087815260200186815260200185600160a060020a0316600160a060020a03168152602001806020018060200180602001848103845287818151815260200191508051906020019080838360005b8381101561025b578181015183820152602001610243565b50505050905090810190601f1680156102885780820380516001836020036101000a031916815260200191505b50848103835286518152865160209182019188019080838360005b838110156102bb5781810151838201526020016102a3565b50505050905090810190601f1680156102e85780820380516001836020036101000a031916815260200191505b50848103825285518152855160209182019187019080838360005b8381101561031b578181015183820152602001610303565b50505050905090810190601f1680156103485780820380516001836020036101000a031916815260200191505b50995050505050505050505060405180910390f35b81801561036957600080fd5b50604080516020600460a43581810135601f810184900484028501840190955284845261044e9482359460248035956044359560643595608435600160a060020a0316953695929460c494920191819084018382808284375050604080516020601f89358b018035918201839004830284018301909452808352979a99988101979196509182019450925082915084018382808284375050604080516020601f89358b018035918201839004830284018301909452808352979a999881019791965091820194509250829150840183828082843750949750610b4a9650505050505050565b60408051918252519081900360200190f35b6000805b86518161ffff161015610527576104fd8989898461ffff1681518110151561048857fe5b9060200190602002015160018a8661ffff168151811015156104a657fe5b906020019060200201516104c38b8860410261ffff1660416110fd565b6104e98b8a8c8b61ffff168151811015156104da57fe5b906020019060200201516110fd565b604080516020810190915260008152610b4a565b50828161ffff1681518110151561051057fe5b602090810290910101519190910190600101610464565b505050505050505050565b60003330146106f85773cafecafecafecafecafecafecafecafecafecafe63d8188a4085336040516020018082600160a060020a0316600160a060020a03166c010000000000000000000000000281526014019150506040516020818303038152906040526040518082805190602001908083835b602083106105c65780518252601f1990920191602091820191016105a7565b51815160209384036101000a60001901801990921691161790526040805192909401829003822063ffffffff891660e060020a0283526004830197909752602482019690965260016044820152915160648084019695509093509183900390910190508186803b15801561063957600080fd5b505af415801561064d573d6000803e3d6000fd5b505050506040513d602081101561066357600080fd5b505115156106f857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f53656e64657220646f6573206e6f742068617665206d616e6167656d656e742060448201527f6b65790000000000000000000000000000000000000000000000000000000000606482015290519081900360840190fd5b6000828152602084815260409182902060028082015482546001808501548751818152608097810188815260038801805494851615610100026000190190941696909604978101889052600160a060020a039094169792968a967f3cf57863a89432c61c4a27073c6ee39e8a764bff5a05aebfbcdcdc80b2e6130a96929593946004820194600590920193919290830190606084019060a0850190889080156107e25780601f106107b7576101008083540402835291602001916107e2565b820191906000526020600020905b8154815290600101906020018083116107c557829003601f168201915b50508481038352865460026000196101006001841615020190911604808252602090910190879080156108565780601f1061082b57610100808354040283529160200191610856565b820191906000526020600020905b81548152906001019060200180831161083957829003601f168201915b50508481038252855460026000196101006001841615020190911604808252602090910190869080156108ca5780601f1061089f576101008083540402835291602001916108ca565b820191906000526020600020905b8154815290600101906020018083116108ad57829003601f168201915b505097505050505050505060405180910390a460008281526020849052604081208181556001810182905560028101805473ffffffffffffffffffffffffffffffffffffffff191690559061092260038301826111f4565b6109306004830160006111f4565b61093e6005830160006111f4565b50600195945050505050565b60008181526020838152604080832080546001808301546002808501546003860180548851601f968216156101000260001901909116939093049485018990048902830189019097528382528897889760609788978897909695600160a060020a039095169492936004830193600590930192918591830182828015610a115780601f106109e657610100808354040283529160200191610a11565b820191906000526020600020905b8154815290600101906020018083116109f457829003601f168201915b5050855460408051602060026001851615610100026000190190941693909304601f810184900484028201840190925281815295985087945092508401905082828015610a9f5780601f10610a7457610100808354040283529160200191610a9f565b820191906000526020600020905b815481529060010190602001808311610a8257829003601f168201915b5050845460408051602060026001851615610100026000190190941693909304601f810184900484028201840190925281815295975086945092508401905082828015610b2d5780601f10610b0257610100808354040283529160200191610b2d565b820191906000526020600020905b815481529060010190602001808311610b1057829003601f168201915b505050505090509550955095509550955095509295509295509295565b60008080333014610db757604080516c01000000000000000000000000330260208083019190915282518083036014018152603490920192839052815191929182918401908083835b60208310610bb25780518252601f199092019160209182019101610b93565b6001836020036101000a0380198251168184511680821785525050505050509050019150506040518091039020915073cafecafecafecafecafecafecafecafecafecafe63d8188a408c8460016040518463ffffffff1660e060020a028152600401808481526020018360001916600019168152602001828152602001935050505060206040518083038186803b158015610c4c57600080fd5b505af4158015610c60573d6000803e3d6000fd5b505050506040513d6020811015610c7657600080fd5b505180610d245750604080517fd8188a40000000000000000000000000000000000000000000000000000000008152600481018d90526024810184905260036044820152905173cafecafecafecafecafecafecafecafecafecafe9163d8188a40916064808301926020929190829003018186803b158015610cf757600080fd5b505af4158015610d0b573d6000803e3d6000fd5b505050506040513d6020811015610d2157600080fd5b50515b1515610db757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f53656e64657220646f6573206e6f74206861766520636c61696d207369676e6560448201527f72206b6579000000000000000000000000000000000000000000000000000000606482015290519081900360840190fd5b86896040516020018083600160a060020a0316600160a060020a03166c01000000000000000000000000028152601401828152602001925050506040516020818303038152906040526040518082805190602001908083835b60208310610e2f5780518252601f199092019160209182019101610e10565b6001836020036101000a0380198251168184511680821785525050505050509050019150506040518091039020905086600160a060020a03168a6000016000836000191660001916815260200190815260200160002060020160009054906101000a9004600160a060020a0316600160a060020a0316141515610ed05760008981526001808c01602090815260408320805492830181558352909120018190555b600081815260208b815260409091208a8155600181018a905560028101805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a038b161790558751610f279260039092019189019061123b565b50600081815260208b815260409091208651610f4b9260049092019188019061123b565b50600081815260208b815260409091208551610f6f9260059092019187019061123b565b5086600160a060020a03168982600019167f46149b18aa084502c3f12bc75e19eda8bda8d102b82cce8474677a6d0d5f43c58b8a8a8a60405180858152602001806020018060200180602001848103845287818151815260200191508051906020019080838360005b83811015610ff0578181015183820152602001610fd8565b50505050905090810190601f16801561101d5780820380516001836020036101000a031916815260200191505b50848103835286518152865160209182019188019080838360005b83811015611050578181015183820152602001611038565b50505050905090810190601f16801561107d5780820380516001836020036101000a031916815260200191505b50848103825285518152855160209182019187019080838360005b838110156110b0578181015183820152602001611098565b50505050905090810190601f1680156110dd5780820380516001836020036101000a031916815260200191505b5097505050505050505060405180910390a49a9950505050505050505050565b606080600080846040519080825280601f01601f191660200182016040528015611131578160200160208202803883390190505b509250600091508590505b8486018110156111e957868181518110151561115457fe5b9060200101517f010000000000000000000000000000000000000000000000000000000000000090047f01000000000000000000000000000000000000000000000000000000000000000283838151811015156111ad57fe5b9060200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053506001918201910161113c565b509095945050505050565b50805460018160011615610100020316600290046000825580601f1061121a5750611238565b601f01602090049060005260206000209081019061123891906112b9565b50565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061127c57805160ff19168380011785556112a9565b828001600101855582156112a9579182015b828111156112a957825182559160200191906001019061128e565b506112b59291506112b9565b5090565b6112d391905b808211156112b557600081556001016112bf565b905600a165627a7a723058201eb76dc86bce9af4d2f0cfe8baa8d87a9b2de6fb247c8bebccc3567efbd787610029" +} diff --git a/services/server/test/testcontracts/LibrariesPreSolidity050/metadata.json b/services/server/test/testcontracts/LibrariesPreSolidity050/metadata.json new file mode 100644 index 000000000..ee732f36c --- /dev/null +++ b/services/server/test/testcontracts/LibrariesPreSolidity050/metadata.json @@ -0,0 +1 @@ +{"compiler":{"version":"0.4.26+commit.4563c3fc"},"language":"Solidity","output":{"abi":[{"constant":false,"inputs":[{"name":"_keyHolderData","type":"KeyHolderLibrary.KeyHolderData storage"},{"name":"_claims","type":"ClaimHolderLibrary.Claims storage"},{"name":"_topic","type":"uint256[]"},{"name":"_issuer","type":"address[]"},{"name":"_signature","type":"bytes"},{"name":"_data","type":"bytes"},{"name":"_offsets","type":"uint256[]"}],"name":"addClaims","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_keyHolderData","type":"KeyHolderLibrary.KeyHolderData storage"},{"name":"_claims","type":"ClaimHolderLibrary.Claims storage"},{"name":"_claimId","type":"bytes32"}],"name":"removeClaim","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_claims","type":"ClaimHolderLibrary.Claims storage"},{"name":"_claimId","type":"bytes32"}],"name":"getClaim","outputs":[{"name":"topic","type":"uint256"},{"name":"scheme","type":"uint256"},{"name":"issuer","type":"address"},{"name":"signature","type":"bytes"},{"name":"data","type":"bytes"},{"name":"uri","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_keyHolderData","type":"KeyHolderLibrary.KeyHolderData storage"},{"name":"_claims","type":"ClaimHolderLibrary.Claims storage"},{"name":"_topic","type":"uint256"},{"name":"_scheme","type":"uint256"},{"name":"_issuer","type":"address"},{"name":"_signature","type":"bytes"},{"name":"_data","type":"bytes"},{"name":"_uri","type":"string"}],"name":"addClaim","outputs":[{"name":"claimRequestId","type":"bytes32"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"claimId","type":"bytes32"},{"indexed":true,"name":"topic","type":"uint256"},{"indexed":false,"name":"scheme","type":"uint256"},{"indexed":true,"name":"issuer","type":"address"},{"indexed":false,"name":"signature","type":"bytes"},{"indexed":false,"name":"data","type":"bytes"},{"indexed":false,"name":"uri","type":"string"}],"name":"ClaimAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"claimId","type":"bytes32"},{"indexed":true,"name":"topic","type":"uint256"},{"indexed":false,"name":"scheme","type":"uint256"},{"indexed":true,"name":"issuer","type":"address"},{"indexed":false,"name":"signature","type":"bytes"},{"indexed":false,"name":"data","type":"bytes"},{"indexed":false,"name":"uri","type":"string"}],"name":"ClaimRemoved","type":"event"}],"devdoc":{"methods":{}},"userdoc":{"methods":{}}},"settings":{"compilationTarget":{"ClaimHolderLibrary.sol":"ClaimHolderLibrary"},"evmVersion":"byzantium","libraries":{},"optimizer":{"enabled":true,"runs":200},"remappings":[]},"sources":{"ClaimHolderLibrary.sol":{"keccak256":"0x2eb3b536dc5a2a4d7efe258f62e14a83812ca2c736d9746f321124fc4d075363","urls":["bzzr://beccdfc5376445c036ef5132f45c1915cdccc6c9941077ea2a1a69c630350fc4"]}},"version":1} \ No newline at end of file diff --git a/services/server/test/testcontracts/LibrariesPreSolidity050/sources/ClaimHolderLibrary.sol b/services/server/test/testcontracts/LibrariesPreSolidity050/sources/ClaimHolderLibrary.sol new file mode 100644 index 000000000..3de4e66a3 --- /dev/null +++ b/services/server/test/testcontracts/LibrariesPreSolidity050/sources/ClaimHolderLibrary.sol @@ -0,0 +1,375 @@ +pragma solidity ^0.4.24; + +library KeyHolderLibrary { + event KeyAdded(bytes32 indexed key, uint256 indexed purpose, uint256 indexed keyType); + event KeyRemoved(bytes32 indexed key, uint256 indexed purpose, uint256 indexed keyType); + event ExecutionRequested(uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data); + event ExecutionFailed(uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data); + event Executed(uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data); + event Approved(uint256 indexed executionId, bool approved); + + struct Key { + uint256[] purposes; //e.g., MANAGEMENT_KEY = 1, ACTION_KEY = 2, etc. + uint256 keyType; // e.g. 1 = ECDSA, 2 = RSA, etc. + bytes32 key; + } + + struct KeyHolderData { + uint256 executionNonce; + mapping (bytes32 => Key) keys; + mapping (uint256 => bytes32[]) keysByPurpose; + mapping (uint256 => Execution) executions; + } + + struct Execution { + address to; + uint256 value; + bytes data; + bool approved; + bool executed; + } + + function init(KeyHolderData storage _keyHolderData) + public + { + bytes32 _key = keccak256(abi.encodePacked(msg.sender)); + _keyHolderData.keys[_key].key = _key; + _keyHolderData.keys[_key].purposes.push(1); + _keyHolderData.keys[_key].keyType = 1; + _keyHolderData.keysByPurpose[1].push(_key); + emit KeyAdded(_key, 1, 1); + } + + function getKey(KeyHolderData storage _keyHolderData, bytes32 _key) + public + view + returns(uint256[] purposes, uint256 keyType, bytes32 key) + { + return ( + _keyHolderData.keys[_key].purposes, + _keyHolderData.keys[_key].keyType, + _keyHolderData.keys[_key].key + ); + } + + function getKeyPurposes(KeyHolderData storage _keyHolderData, bytes32 _key) + public + view + returns(uint256[] purposes) + { + return (_keyHolderData.keys[_key].purposes); + } + + function getKeysByPurpose(KeyHolderData storage _keyHolderData, uint256 _purpose) + public + view + returns(bytes32[] _keys) + { + return _keyHolderData.keysByPurpose[_purpose]; + } + + function addKey(KeyHolderData storage _keyHolderData, bytes32 _key, uint256 _purpose, uint256 _type) + public + returns (bool success) + { + require(!keyHasPurpose(_keyHolderData, _key, _purpose), "Key already exists with same purpose"); // Key should not already exist with same purpose + if (msg.sender != address(this)) { + require(keyHasPurpose(_keyHolderData, keccak256(abi.encodePacked(msg.sender)), 1), "Sender does not have management key"); // Sender has MANAGEMENT_KEY + } + + _keyHolderData.keys[_key].key = _key; + _keyHolderData.keys[_key].purposes.push(_purpose); + _keyHolderData.keys[_key].keyType = _type; + + _keyHolderData.keysByPurpose[_purpose].push(_key); + + emit KeyAdded(_key, _purpose, _type); + + return true; + } + + function addKeys(KeyHolderData storage _keyHolderData, bytes32[] _keys, uint256 _purpose, uint256 _type) + public + returns (bool success) + { + for (uint16 i = 0; i < _keys.length; i++) { + addKey( + _keyHolderData, + _keys[i], + _purpose, + _type + ); + } + + return true; + } + + function approve(KeyHolderData storage _keyHolderData, uint256 _id, bool _approve) + public + returns (bool success) + { + require(keyHasPurpose(_keyHolderData, keccak256(abi.encodePacked(msg.sender)), 2), "Sender does not have action key"); + require(!_keyHolderData.executions[_id].executed, "Already executed"); + + emit Approved(_id, _approve); + + if (_approve == true) { + _keyHolderData.executions[_id].approved = true; + success = _keyHolderData.executions[_id].to.call(_keyHolderData.executions[_id].data, 0); + if (success) { + _keyHolderData.executions[_id].executed = true; + emit Executed( + _id, + _keyHolderData.executions[_id].to, + _keyHolderData.executions[_id].value, + _keyHolderData.executions[_id].data + ); + return; + } else { + emit ExecutionFailed( + _id, + _keyHolderData.executions[_id].to, + _keyHolderData.executions[_id].value, + _keyHolderData.executions[_id].data + ); + return; + } + } else { + _keyHolderData.executions[_id].approved = false; + } + return true; + } + + function execute(KeyHolderData storage _keyHolderData, address _to, uint256 _value, bytes _data) + public + returns (uint256 executionId) + { + require(!_keyHolderData.executions[_keyHolderData.executionNonce].executed, "Already executed"); + _keyHolderData.executions[_keyHolderData.executionNonce].to = _to; + _keyHolderData.executions[_keyHolderData.executionNonce].value = _value; + _keyHolderData.executions[_keyHolderData.executionNonce].data = _data; + + emit ExecutionRequested(_keyHolderData.executionNonce, _to, _value, _data); + + if (keyHasPurpose(_keyHolderData, keccak256(abi.encodePacked(msg.sender)),1) || keyHasPurpose(_keyHolderData, keccak256(abi.encodePacked(msg.sender)),2)) { + approve(_keyHolderData, _keyHolderData.executionNonce, true); + } + + _keyHolderData.executionNonce++; + return _keyHolderData.executionNonce-1; + } + + function removeKey(KeyHolderData storage _keyHolderData, bytes32 _key, uint256 _purpose) + public + returns (bool success) + { + if (msg.sender != address(this)) { + require(keyHasPurpose(_keyHolderData, keccak256(abi.encodePacked(msg.sender)), 1), "Sender does not have management key"); // Sender has MANAGEMENT_KEY + } + + require(_keyHolderData.keys[_key].key == _key, "No such key"); + emit KeyRemoved(_key, _purpose, _keyHolderData.keys[_key].keyType); + + // Remove purpose from key + uint256[] storage purposes = _keyHolderData.keys[_key].purposes; + for (uint i = 0; i < purposes.length; i++) { + if (purposes[i] == _purpose) { + purposes[i] = purposes[purposes.length - 1]; + delete purposes[purposes.length - 1]; + purposes.length--; + break; + } + } + + // If no more purposes, delete key + if (purposes.length == 0) { + delete _keyHolderData.keys[_key]; + } + + // Remove key from keysByPurpose + bytes32[] storage keys = _keyHolderData.keysByPurpose[_purpose]; + for (uint j = 0; j < keys.length; j++) { + if (keys[j] == _key) { + keys[j] = keys[keys.length - 1]; + delete keys[keys.length - 1]; + keys.length--; + break; + } + } + + return true; + } + + function keyHasPurpose(KeyHolderData storage _keyHolderData, bytes32 _key, uint256 _purpose) + public + view + returns(bool result) + { + bool isThere; + if (_keyHolderData.keys[_key].key == 0) { + return false; + } + + uint256[] storage purposes = _keyHolderData.keys[_key].purposes; + for (uint i = 0; i < purposes.length; i++) { + if (purposes[i] <= _purpose) { + isThere = true; + break; + } + } + return isThere; + } +} + +library ClaimHolderLibrary { + event ClaimAdded(bytes32 indexed claimId, uint256 indexed topic, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri); + event ClaimRemoved(bytes32 indexed claimId, uint256 indexed topic, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri); + + struct Claim { + uint256 topic; + uint256 scheme; + address issuer; // msg.sender + bytes signature; // this.address + topic + data + bytes data; + string uri; + } + + struct Claims { + mapping (bytes32 => Claim) byId; + mapping (uint256 => bytes32[]) byTopic; + } + + function addClaim( + KeyHolderLibrary.KeyHolderData storage _keyHolderData, + Claims storage _claims, + uint256 _topic, + uint256 _scheme, + address _issuer, + bytes _signature, + bytes _data, + string _uri + ) + public + returns (bytes32 claimRequestId) + { + if (msg.sender != address(this)) { + bytes32 sender = keccak256(abi.encodePacked(msg.sender)); + require(KeyHolderLibrary.keyHasPurpose(_keyHolderData, sender, 1) || + KeyHolderLibrary.keyHasPurpose(_keyHolderData, sender, 3), "Sender does not have claim signer key"); + } + + bytes32 claimId = keccak256(abi.encodePacked(_issuer, _topic)); + + if (_claims.byId[claimId].issuer != _issuer) { + _claims.byTopic[_topic].push(claimId); + } + + _claims.byId[claimId].topic = _topic; + _claims.byId[claimId].scheme = _scheme; + _claims.byId[claimId].issuer = _issuer; + _claims.byId[claimId].signature = _signature; + _claims.byId[claimId].data = _data; + _claims.byId[claimId].uri = _uri; + + emit ClaimAdded( + claimId, + _topic, + _scheme, + _issuer, + _signature, + _data, + _uri + ); + + return claimId; + } + + function addClaims( + KeyHolderLibrary.KeyHolderData storage _keyHolderData, + Claims storage _claims, + uint256[] _topic, + address[] _issuer, + bytes _signature, + bytes _data, + uint256[] _offsets + ) + public + { + uint offset = 0; + for (uint16 i = 0; i < _topic.length; i++) { + addClaim( + _keyHolderData, + _claims, + _topic[i], + 1, + _issuer[i], + getBytes(_signature, (i * 65), 65), + getBytes(_data, offset, _offsets[i]), + "" + ); + offset += _offsets[i]; + } + } + + function removeClaim( + KeyHolderLibrary.KeyHolderData storage _keyHolderData, + Claims storage _claims, + bytes32 _claimId + ) + public + returns (bool success) + { + if (msg.sender != address(this)) { + require(KeyHolderLibrary.keyHasPurpose(_keyHolderData, keccak256(abi.encodePacked(msg.sender)), 1), "Sender does not have management key"); + } + + emit ClaimRemoved( + _claimId, + _claims.byId[_claimId].topic, + _claims.byId[_claimId].scheme, + _claims.byId[_claimId].issuer, + _claims.byId[_claimId].signature, + _claims.byId[_claimId].data, + _claims.byId[_claimId].uri + ); + + delete _claims.byId[_claimId]; + return true; + } + + function getClaim(Claims storage _claims, bytes32 _claimId) + public + view + returns( + uint256 topic, + uint256 scheme, + address issuer, + bytes signature, + bytes data, + string uri + ) + { + return ( + _claims.byId[_claimId].topic, + _claims.byId[_claimId].scheme, + _claims.byId[_claimId].issuer, + _claims.byId[_claimId].signature, + _claims.byId[_claimId].data, + _claims.byId[_claimId].uri + ); + } + + function getBytes(bytes _str, uint256 _offset, uint256 _length) + internal + pure + returns (bytes) + { + bytes memory sig = new bytes(_length); + uint256 j = 0; + for (uint256 k = _offset; k < _offset + _length; k++) { + sig[j] = _str[k]; + j++; + } + return sig; + } +} \ No newline at end of file diff --git a/services/server/test/verifier-alliance/libraries_manually_linked.json b/services/server/test/verifier-alliance/libraries_manually_linked.json index b23aee733..6cc42a95e 100644 --- a/services/server/test/verifier-alliance/libraries_manually_linked.json +++ b/services/server/test/verifier-alliance/libraries_manually_linked.json @@ -136,7 +136,7 @@ "creation_match": true, "creation_values": { "libraries": { - "__$b8833469cd54bfd61b3a18436a18bad1f3$__": "0x7d53f102f4d4aa014db4e10d6deec2009b3cda6b" + "contracts/1_Storage.sol:Journal": "0x7d53f102f4d4aa014db4e10d6deec2009b3cda6b" } }, "creation_transformations": [ @@ -144,7 +144,7 @@ "type": "replace", "reason": "library", "offset": 217, - "id": "__$b8833469cd54bfd61b3a18436a18bad1f3$__" + "id": "contracts/1_Storage.sol:Journal" } ], "creation_metadata_match": true, @@ -152,7 +152,7 @@ "runtime_match": true, "runtime_values": { "libraries": { - "__$b8833469cd54bfd61b3a18436a18bad1f3$__": "0x7d53f102f4d4aa014db4e10d6deec2009b3cda6b" + "contracts/1_Storage.sol:Journal": "0x7d53f102f4d4aa014db4e10d6deec2009b3cda6b" } }, "runtime_transformations": [ @@ -160,7 +160,7 @@ "type": "replace", "reason": "library", "offset": 185, - "id": "__$b8833469cd54bfd61b3a18436a18bad1f3$__" + "id": "contracts/1_Storage.sol:Journal" } ], "runtime_metadata_match": true