diff --git a/.changeset/healthy-crabs-unite.md b/.changeset/healthy-crabs-unite.md new file mode 100644 index 000000000..1c12d08b3 --- /dev/null +++ b/.changeset/healthy-crabs-unite.md @@ -0,0 +1,5 @@ +--- +"@onflow/fcl": minor +--- + +Add support for running FLIX v1.1.0 (verified interactions) diff --git a/packages/fcl/src/exec/utils/derive-dependencies.js b/packages/fcl/src/exec/utils/derive-dependencies.js index 20f122e83..de8576166 100644 --- a/packages/fcl/src/exec/utils/derive-dependencies.js +++ b/packages/fcl/src/exec/utils/derive-dependencies.js @@ -44,6 +44,25 @@ export async function deriveDependencies(opts = {}) { return derivedDependencies + case "1.1.0": + template?.data?.dependencies?.forEach(dependency => { + dependency.contracts.forEach(contract => { + const contractName = contract.contract + contract.networks.forEach(net => { + if (net.network === network) { + derivedDependencies[contractName] = withPrefix(net?.address) + } + }) + + invariant( + derivedDependencies[contractName], + `networkAddress -- Could not find contracts Network Address: ${network} ${contractName}` + ) + }) + }) + + return derivedDependencies + default: throw new Error( "FCL configureDependencies Error: Unsupported template version" diff --git a/packages/fcl/src/interaction-template-utils/derive-cadence-by-network.js b/packages/fcl/src/interaction-template-utils/derive-cadence-by-network.js index ba6376936..a20be3852 100644 --- a/packages/fcl/src/interaction-template-utils/derive-cadence-by-network.js +++ b/packages/fcl/src/interaction-template-utils/derive-cadence-by-network.js @@ -62,13 +62,48 @@ export function deriveCadenceByNetwork({network, template}) { return [dependencyPlaceholder, dependencyContractForNetwork.address] } - ) - + ) + return networkDependencies.reduce((cadence, [placeholder, address]) => { const regex = new RegExp("(\\b" + placeholder + "\\b)", "g") return cadence.replace(regex, address) }, template.data.cadence) + case "1.1.0": + // get network dependencies from template dependencies, use new string import format + const networkDeps = {} + + template?.data?.dependencies.forEach(dependency => { + dependency.contracts.forEach(contract => { + const contractName = contract.contract + contract.networks.forEach(net => { + if (net.network === network) { + networkDeps[contractName] = net.address + } + }) + + invariant( + networkDeps[contractName], + `networkAddress -- Could not find contracts Network Address: ${network} ${contractName}` + ) + }) + }) + + invariant( + Object.keys(networkDeps).length === template?.data?.dependencies.length, + `networkDeps -- Could not find contracts for import dependencies: ${networkDeps}` + ) + + invariant( + Object.keys(networkDeps).length === Object.values(networkDeps).length, + `networkDeps -- Could not find all addresses for network ${network} dependencies: ${networkDeps}` + ) + + return Object.keys(networkDeps).reduce((cadence, contractName) => { + const test = new RegExp(`\\bimport\\b\\s*\\\"${contractName}\\\"`, "g") + return cadence.replace(test, `import ${contractName} from ${networkDeps[contractName]}`) + }, template.data.cadence.body) + default: throw new Error( "deriveCadenceByNetwork Error: Unsupported template version" diff --git a/packages/fcl/src/interaction-template-utils/derive-cadence-by-network.test.js b/packages/fcl/src/interaction-template-utils/derive-cadence-by-network.test.js index b21c67b6d..518012ac9 100644 --- a/packages/fcl/src/interaction-template-utils/derive-cadence-by-network.test.js +++ b/packages/fcl/src/interaction-template-utils/derive-cadence-by-network.test.js @@ -1,6 +1,6 @@ import {deriveCadenceByNetwork} from "./derive-cadence-by-network.js" -describe("Derive cadence by network", () => { +describe("Derive cadence by network 1.0.0", () => { let template = { f_type: "InteractionTemplate", f_version: "1.0.0", @@ -52,3 +52,259 @@ describe("Derive cadence by network", () => { ).toThrow(Error) }) }) + +describe("Derive cadence by network 1.1.0 single import", () => { + let templatev11 = { + f_type: "InteractionTemplate", + f_version: "1.1.0", + id: "a2b2d73def...aabc5472d2", + data: { + type: "transaction", + interface: "asadf23234...fas234234", + messages: [], + cadence: { + body: "import \"FlowToken\"\n transaction(amount: UFix64, to: Address) {\n let vault: @FungibleToken.Vault\n prepare(signer: AuthAccount) {\n %%self.vault <- signer\n .borrow<&{FungibleToken.Provider}>(from: /storage/flowTokenVault)!\n .withdraw(amount: amount)\n self.vault <- FungibleToken.getVault(signer)\n }\n execute {\n getAccount(to)\n .getCapability(/public/flowTokenReceiver)!\n .borrow<&{FungibleToken.Receiver}>()!\n .deposit(from: <-self.vault)\n }\n }", + pins: [ + { + network: "mainnet", + pin: "186e262ce6fe06b5075ec6569a0e5482a79c471881182612d8e4a665c2977f3e", + }, + { + network: "testnet", + pin: "f93977d7a297f559e97259cb2a95fed0f87cfeec46c5257a26adc26a260d6c4c", + }, + ], + }, + dependencies: [ + { + contracts: [ + { + contract: "FlowToken", + networks: [ + { + network: "mainnet", + address: "0x1654653399040a61", + dependency_pin_block_height: 10123123123, + dependency_pin: { + pin: "c8cb7cc7a1c2a329de65d83455016bc3a9b53f9668c74ef555032804bac0b25b", + pin_self: + "38d0cca4b74c4e88213df636b4cfc2eb6e86fd8b2b84579d3b9bffab3e0b1fcb", + pin_contract_name: "FlowToken", + imports: [ + { + pin: "b8a3ed26c222ed67016a28021d8fee5603b948533cbc992b3c90f71a61b2b312", + pin_self: + "7bc3056ba5d39d130f45411c2c05bb549db8ce727c11a1cb821136a621be27fb", + pin_contract_name: "FungibleToken", + pin_contract_address: "0xf233dcee88fe0abe", + imports: [], + }, + ], + }, + }, + { + network: "testnet", + address: "0x7e60df042a9c0868", + dependency_pin_block_height: 10123123123, + dependency_pin: { + pin: "c8cb7cc7a1c2a329de65d83455016bc3a9b53f9668c74ef555032804bac0b25b", + pin_self: + "38d0cca4b74c4e88213df636b4cfc2eb6e86fd8b2b84579d3b9bffab3e0b1fcb", + pin_contract_name: "FlowToken", + pin_contract_address: "0x7e60df042a9c0868", + imports: [], + }, + }, + ], + }, + ], + }, + ], + parameters: [ + { + label: "amount", + index: 0, + type: "UFix64", + messages: [], + balance: "FlowToken", + }, + { + label: "to", + index: 1, + type: "Address", + messages: [], + }, + ], + }, + } + + const mainnetAddressReplaced = "import FlowToken from 0x1654653399040a61\n transaction(amount: UFix64, to: Address) {\n let vault: @FungibleToken.Vault\n prepare(signer: AuthAccount) {\n %%self.vault <- signer\n .borrow<&{FungibleToken.Provider}>(from: /storage/flowTokenVault)!\n .withdraw(amount: amount)\n self.vault <- FungibleToken.getVault(signer)\n }\n execute {\n getAccount(to)\n .getCapability(/public/flowTokenReceiver)!\n .borrow<&{FungibleToken.Receiver}>()!\n .deposit(from: <-self.vault)\n }\n }" + test("1.1.0 derives cadence correctly for a given mainnet", async () => { + let cadence = deriveCadenceByNetwork({ + network: "mainnet", + template: templatev11, + }) + + expect(cadence).toEqual(mainnetAddressReplaced) + + + }) + + const testnetAddressReplaced = "import FlowToken from 0x7e60df042a9c0868\n transaction(amount: UFix64, to: Address) {\n let vault: @FungibleToken.Vault\n prepare(signer: AuthAccount) {\n %%self.vault <- signer\n .borrow<&{FungibleToken.Provider}>(from: /storage/flowTokenVault)!\n .withdraw(amount: amount)\n self.vault <- FungibleToken.getVault(signer)\n }\n execute {\n getAccount(to)\n .getCapability(/public/flowTokenReceiver)!\n .borrow<&{FungibleToken.Receiver}>()!\n .deposit(from: <-self.vault)\n }\n }" + test("1.1.0 derives cadence correctly for a given testnet", async () => { + let cadence = deriveCadenceByNetwork({ + network: "testnet", + template: templatev11, + }) + + expect(cadence).toEqual(testnetAddressReplaced) + }) + + test("It fails to derive cadence for unknown network", async () => { + expect(() => + deriveCadenceByNetwork({ + network: "randomnet", + templatev11, + }) + ).toThrow(Error) + }) +}) + + +describe("Derive cadence by network 1.1.0 multiple import", () => { + let templatev11 = { + f_type: "InteractionTemplate", + f_version: "1.1.0", + id: "a2b2d73def...aabc5472d2", + data: { + type: "transaction", + interface: "asadf23234...fas234234", + messages: [], + cadence: { + body: "import \"FlowToken\"\n import \"FungibleToken\"\n transaction(amount: UFix64, to: Address) {\n let vault: @FungibleToken.Vault\n prepare(signer: AuthAccount) {\n %%self.vault <- signer\n .borrow<&{FungibleToken.Provider}>(from: /storage/flowTokenVault)!\n .withdraw(amount: amount)\n self.vault <- FungibleToken.getVault(signer)\n }\n execute {\n getAccount(to)\n .getCapability(/public/flowTokenReceiver)!\n .borrow<&{FungibleToken.Receiver}>()!\n .deposit(from: <-self.vault)\n }\n }", + pins: [ + { + network: "mainnet", + pin: "186e262ce6fe06b5075ec6569a0e5482a79c471881182612d8e4a665c2977f3e", + }, + { + network: "testnet", + pin: "f93977d7a297f559e97259cb2a95fed0f87cfeec46c5257a26adc26a260d6c4c", + }, + ], + }, + dependencies: [ + { + contracts: [ + { + contract: "FlowToken", + networks: [ + { + network: "mainnet", + address: "0x1654653399040a61", + dependency_pin_block_height: 10123123123, + dependency_pin: { + pin: "c8cb7cc7a1c2a329de65d83455016bc3a9b53f9668c74ef555032804bac0b25b", + pin_self: + "38d0cca4b74c4e88213df636b4cfc2eb6e86fd8b2b84579d3b9bffab3e0b1fcb", + pin_contract_name: "FlowToken", + imports: [], + }, + }, + { + network: "testnet", + address: "0x7e60df042a9c0868", + dependency_pin_block_height: 10123123123, + dependency_pin: { + pin: "c8cb7cc7a1c2a329de65d83455016bc3a9b53f9668c74ef555032804bac0b25b", + pin_self: + "38d0cca4b74c4e88213df636b4cfc2eb6e86fd8b2b84579d3b9bffab3e0b1fcb", + pin_contract_name: "FlowToken", + pin_contract_address: "0x7e60df042a9c0868", + imports: [], + }, + }, + ], + }, + { + contract: "FungibleToken", + networks: [ + { + network: "mainnet", + address: "0xf233dcee88fe0abe", + dependency_pin_block_height: 10123123123, + dependency_pin: { + pin: "c8cb7cc7a1c2a329de65d83455016bc3a9b53f9668c74ef555032804bac0b25b", + pin_self: + "38d0cca4b74c4e88213df636b4cfc2eb6e86fd8b2b84579d3b9bffab3e0b1fcb", + pin_contract_name: "FlowToken", + imports: [], + }, + }, + { + network: "testnet", + address: "0x11111111111", + dependency_pin_block_height: 10123123123, + dependency_pin: { + pin: "c8cb7cc7a1c2a329de65d83455016bc3a9b53f9668c74ef555032804bac0b25b", + pin_self: + "38d0cca4b74c4e88213df636b4cfc2eb6e86fd8b2b84579d3b9bffab3e0b1fcb", + pin_contract_name: "FlowToken", + pin_contract_address: "0x7e60df042a9c0868", + imports: [], + }, + }, + ], + }, + ], + }, + ], + parameters: [ + { + label: "amount", + index: 0, + type: "UFix64", + messages: [], + balance: "FlowToken", + }, + { + label: "to", + index: 1, + type: "Address", + messages: [], + }, + ], + }, + } + + const mainnetAddressReplaced = "import FlowToken from 0x1654653399040a61\n import FungibleToken from 0xf233dcee88fe0abe\n transaction(amount: UFix64, to: Address) {\n let vault: @FungibleToken.Vault\n prepare(signer: AuthAccount) {\n %%self.vault <- signer\n .borrow<&{FungibleToken.Provider}>(from: /storage/flowTokenVault)!\n .withdraw(amount: amount)\n self.vault <- FungibleToken.getVault(signer)\n }\n execute {\n getAccount(to)\n .getCapability(/public/flowTokenReceiver)!\n .borrow<&{FungibleToken.Receiver}>()!\n .deposit(from: <-self.vault)\n }\n }" + test("1.1.0 multiple imports derives cadence correctly for a given mainnet", async () => { + let cadence = deriveCadenceByNetwork({ + network: "mainnet", + template: templatev11, + }) + + expect(cadence).toEqual(mainnetAddressReplaced) + + + }) + + const testnetAddressReplaced = "import FlowToken from 0x7e60df042a9c0868\n import FungibleToken from 0x11111111111\n transaction(amount: UFix64, to: Address) {\n let vault: @FungibleToken.Vault\n prepare(signer: AuthAccount) {\n %%self.vault <- signer\n .borrow<&{FungibleToken.Provider}>(from: /storage/flowTokenVault)!\n .withdraw(amount: amount)\n self.vault <- FungibleToken.getVault(signer)\n }\n execute {\n getAccount(to)\n .getCapability(/public/flowTokenReceiver)!\n .borrow<&{FungibleToken.Receiver}>()!\n .deposit(from: <-self.vault)\n }\n }" + test("1.1.0 multiple imports derives cadence correctly for a given testnet", async () => { + let cadence = deriveCadenceByNetwork({ + network: "testnet", + template: templatev11, + }) + + expect(cadence).toEqual(testnetAddressReplaced) + }) + + test("1.1.0 multiple imports fails to derive cadence for unknown network", async () => { + expect(() => + deriveCadenceByNetwork({ + network: "randomnet", + templatev11, + }) + ).toThrow(Error) + }) +}) \ No newline at end of file diff --git a/packages/fcl/src/normalizers/interaction-template/interaction-template.js b/packages/fcl/src/normalizers/interaction-template/interaction-template.js index 459906df1..3b32bd912 100644 --- a/packages/fcl/src/normalizers/interaction-template/interaction-template.js +++ b/packages/fcl/src/normalizers/interaction-template/interaction-template.js @@ -4,7 +4,8 @@ export function normalizeInteractionTemplate(template) { switch (template["f_version"]) { case "1.0.0": return template - + case "1.1.0": + return template default: throw new Error( "normalizeInteractionTemplate Error: Invalid InteractionTemplate"