diff --git a/contracts/MigrationContractStaging.cdc b/contracts/MigrationContractStaging.cdc index 106ac3a..406c0be 100644 --- a/contracts/MigrationContractStaging.cdc +++ b/contracts/MigrationContractStaging.cdc @@ -156,6 +156,15 @@ access(all) contract MigrationContractStaging { return self.getStagedContractUpdate(address: address, name: name)?.code } + /// Returns the staged contract code hash for the given address and name or nil if it's not staged + /// + access(all) view fun getStagedContractCodeHash(address: Address, name: String): String? { + if let update = self.getStagedContractUpdate(address: address, name: name) { + return self.getCodeHash(update.code) + } + return nil + } + /// Returns the ContractUpdate struct for the given contract if it's been staged. /// access(all) view fun getStagedContractUpdate(address: Address, name: String): ContractUpdate? { @@ -207,6 +216,14 @@ access(all) contract MigrationContractStaging { ?? panic("Could not derive Capsule StoragePath for given address") } + /* --- Util --- */ + + /// Returns the hash of the given code, hashing with SHA3-256 + /// + access(all) fun getCodeHash(_ code: String): String { + return String.encodeHex(HashAlgorithm.SHA3_256.hash(code.utf8)) + } + /* ------------------------------------------------------------------------------------------------------------ */ /* ------------------------------------------------ Constructs ------------------------------------------------ */ /* ------------------------------------------------------------------------------------------------------------ */ @@ -352,7 +369,7 @@ access(all) contract MigrationContractStaging { emit StagingStatusUpdated( capsuleUUID: self.uuid, address: self.update.address, - code: code, + code: MigrationContractStaging.getCodeHash(code), contract: self.update.name, action: "replace" ) @@ -406,7 +423,7 @@ access(all) contract MigrationContractStaging { emit StagingStatusUpdated( capsuleUUID: capsule.uuid, address: host.address(), - code: code, + code: MigrationContractStaging.getCodeHash(code), contract: name, action: "stage" ) diff --git a/scripts/migration-contract-staging/get_staged_contract_code_hash.cdc b/scripts/migration-contract-staging/get_staged_contract_code_hash.cdc new file mode 100644 index 0000000..b414d44 --- /dev/null +++ b/scripts/migration-contract-staging/get_staged_contract_code_hash.cdc @@ -0,0 +1,14 @@ +import "MigrationContractStaging" + +#interaction ( + version: "1.1.0", + title: "Get Staged Contract Code", + description: "Returns the Cadence code that has been staged for the given contract or nil if it is not yet staged.", + language: "en-US", +) + +/// Returns the code hash as it is staged or nil if it not currently staged. +/// +access(all) fun main(contractAddress: Address, contractName: String): String? { + return MigrationContractStaging.getStagedContractCodeHash(address: contractAddress, name: contractName) +} diff --git a/tests/migration_contract_staging_tests.cdc b/tests/migration_contract_staging_tests.cdc index 7ced197..5133a79 100644 --- a/tests/migration_contract_staging_tests.cdc +++ b/tests/migration_contract_staging_tests.cdc @@ -13,12 +13,16 @@ access(all) let bcAccount = Test.getAccount(0x0000000000000010) // Content of update contracts as hex strings access(all) let fooUpdateCode = "61636365737328616c6c2920636f6e747261637420466f6f207b0a2020202061636365737328616c6c292066756e20666f6f28293a20537472696e67207b0a202020202020202072657475726e2022626172220a202020207d0a7d0a" access(all) let fooUpdateCadence = String.fromUTF8(fooUpdateCode.decodeHex()) ?? panic("Problem decoding fooUpdateCode") +access(all) var fooUpdateHash = "" access(all) let aUpdateCode = "61636365737328616c6c2920636f6e747261637420696e746572666163652041207b0a202020200a2020202061636365737328616c6c29207265736f7572636520696e746572666163652049207b0a202020202020202061636365737328616c6c292066756e20666f6f28293a20537472696e670a202020202020202061636365737328616c6c292066756e2062617228293a20537472696e670a202020207d0a0a2020202061636365737328616c6c29207265736f757263652052203a2049207b0a202020202020202061636365737328616c6c292066756e20666f6f28293a20537472696e67207b0a20202020202020202020202072657475726e2022666f6f220a20202020202020207d0a202020202020202061636365737328616c6c292066756e2062617228293a20537472696e67207b0a20202020202020202020202072657475726e2022626172220a20202020202020207d0a202020207d0a7d" access(all) let aUpdateCadence = String.fromUTF8(aUpdateCode.decodeHex()) ?? panic("Problem decoding aUpdateCode") +access(all) var aUpdateHash = "" access(all) let bUpdateCode = "696d706f727420412066726f6d203078303030303030303030303030303030390a0a61636365737328616c6c2920636f6e74726163742042203a2041207b0a202020200a2020202061636365737328616c6c29207265736f757263652052203a20412e49207b0a202020202020202061636365737328616c6c292066756e20666f6f28293a20537472696e67207b0a20202020202020202020202072657475726e2022666f6f220a20202020202020207d0a202020202020202061636365737328616c6c292066756e2062617228293a20537472696e67207b0a20202020202020202020202072657475726e2022626172220a20202020202020207d0a202020207d0a202020200a2020202061636365737328616c6c292066756e206372656174655228293a204052207b0a202020202020202072657475726e203c2d637265617465205228290a202020207d0a7d" access(all) let bUpdateCadence = String.fromUTF8(bUpdateCode.decodeHex()) ?? panic("Problem decoding bUpdateCode") +access(all) var bUpdateHash = "" access(all) let cUpdateCode = "696d706f727420412066726f6d203078303030303030303030303030303030390a696d706f727420422066726f6d203078303030303030303030303030303031300a0a61636365737328616c6c2920636f6e74726163742043207b0a0a2020202061636365737328616c6c29206c65742053746f72616765506174683a2053746f72616765506174680a2020202061636365737328616c6c29206c6574205075626c6963506174683a205075626c6963506174680a0a2020202061636365737328616c6c29207265736f7572636520696e74657266616365204f757465725075626c6963207b0a202020202020202061636365737328616c6c292066756e20676574466f6f46726f6d2869643a2055496e743634293a20537472696e670a202020202020202061636365737328616c6c292066756e2067657442617246726f6d2869643a2055496e743634293a20537472696e670a202020207d0a0a2020202061636365737328616c6c29207265736f75726365204f75746572203a204f757465725075626c6963207b0a202020202020202061636365737328616c6c29206c657420696e6e65723a20407b55496e7436343a20412e527d0a0a2020202020202020696e69742829207b0a20202020202020202020202073656c662e696e6e6572203c2d207b7d0a20202020202020207d0a0a202020202020202061636365737328616c6c292066756e20676574466f6f46726f6d2869643a2055496e743634293a20537472696e67207b0a20202020202020202020202072657475726e2073656c662e626f72726f775265736f75726365286964293f2e666f6f2829203f3f2070616e696328224e6f207265736f7572636520666f756e64207769746820676976656e20494422290a20202020202020207d0a0a202020202020202061636365737328616c6c292066756e2067657442617246726f6d2869643a2055496e743634293a20537472696e67207b0a20202020202020202020202072657475726e2073656c662e626f72726f775265736f75726365286964293f2e6261722829203f3f2070616e696328224e6f207265736f7572636520666f756e64207769746820676976656e20494422290a20202020202020207d0a0a202020202020202061636365737328616c6c292066756e206164645265736f75726365285f20693a2040412e5229207b0a20202020202020202020202073656c662e696e6e65725b692e757569645d203c2d2120690a20202020202020207d0a0a202020202020202061636365737328616c6c292066756e20626f72726f775265736f75726365285f2069643a2055496e743634293a20267b412e497d3f207b0a20202020202020202020202072657475726e202673656c662e696e6e65725b69645d20617320267b412e497d3f0a20202020202020207d0a0a202020202020202061636365737328616c6c292066756e2072656d6f76655265736f75726365285f2069643a2055496e743634293a2040412e523f207b0a20202020202020202020202072657475726e203c2d2073656c662e696e6e65722e72656d6f7665286b65793a206964290a20202020202020207d0a0a202020202020202064657374726f792829207b0a20202020202020202020202064657374726f792073656c662e696e6e65720a20202020202020207d0a202020207d0a0a20202020696e69742829207b0a202020202020202073656c662e53746f7261676550617468203d202f73746f726167652f4f757465720a202020202020202073656c662e5075626c696350617468203d202f7075626c69632f4f757465725075626c69630a0a202020202020202073656c662e6163636f756e742e736176653c404f757465723e283c2d637265617465204f7574657228292c20746f3a2073656c662e53746f7261676550617468290a202020202020202073656c662e6163636f756e742e6c696e6b3c267b4f757465725075626c69637d3e2873656c662e5075626c6963506174682c207461726765743a2073656c662e53746f7261676550617468290a0a20202020202020206c6574206f75746572203d2073656c662e6163636f756e742e626f72726f773c264f757465723e2866726f6d3a2073656c662e53746f726167655061746829210a20202020202020206f757465722e6164645265736f75726365283c2d20422e637265617465522829290a202020207d0a7d" access(all) let cUpdateCadence = String.fromUTF8(cUpdateCode.decodeHex()) ?? panic("Problem decoding cUpdateCode") +access(all) var cUpdateHash = "" // Block height different to add to the staging cutoff access(all) let blockHeightDelta: UInt64 = 10 @@ -75,6 +79,8 @@ access(all) fun testStageContractSucceeds() { // Take snapshot as the following test case will stage this same transaction via Host Capability snapshotHeight = getCurrentBlockHeight() + fooUpdateHash = MigrationContractStaging.getCodeHash(fooUpdateCadence) + let txResult = executeTransaction( "../transactions/migration-contract-staging/stage_contract.cdc", ["Foo", fooUpdateCadence], @@ -92,7 +98,10 @@ access(all) fun testStageContractSucceeds() { let fooStagedContractCode = getStagedContractCode(contractAddress: fooAccount.address, contractName: "Foo") ?? panic("Problem retrieving result of getStagedContractCode()") + let fooStagedContractHash = getStagedContractCodeHash(contractAddress: fooAccount.address, contractName: "Foo") + ?? panic("Problem retrieving result of getStagedContractCodeHash()") Test.assertEqual(fooUpdateCadence, fooStagedContractCode) + Test.assertEqual(fooUpdateHash, fooStagedContractHash) let allStagedCodeForFooAccount = getAllStagedContractCodeForAddress(contractAddress: fooAccount.address) assertStagedContractCodeEqual({ "Foo": fooUpdateCadence}, allStagedCodeForFooAccount) @@ -102,7 +111,7 @@ access(all) fun testStageContractSucceeds() { let evt = events[0] as! MigrationContractStaging.StagingStatusUpdated Test.assertEqual(fooAccount.address, evt.address) - Test.assertEqual(fooUpdateCadence, evt.code) + Test.assertEqual(fooUpdateHash, evt.code) Test.assertEqual("Foo", evt.contract) Test.assertEqual("stage", evt.action) } @@ -153,13 +162,17 @@ access(all) fun testStageContractViaHostCapabilitySucceeds() { let evt = events[0] as! MigrationContractStaging.StagingStatusUpdated Test.assertEqual(fooAccount.address, evt.address) - Test.assertEqual(fooUpdateCadence, evt.code) + Test.assertEqual(fooUpdateHash, evt.code) Test.assertEqual("Foo", evt.contract) Test.assertEqual("stage", evt.action) } access(all) fun testStageMultipleContractsSucceeds() { + aUpdateHash = MigrationContractStaging.getCodeHash(aUpdateCadence) + bUpdateHash = MigrationContractStaging.getCodeHash(bUpdateCadence) + cUpdateHash = MigrationContractStaging.getCodeHash(cUpdateCadence) + // Demonstrating staging multiple contracts on the same host & out of dependency order let cStagingTxResult = executeTransaction( "../transactions/migration-contract-staging/stage_contract.cdc", @@ -188,17 +201,17 @@ access(all) fun testStageMultipleContractsSucceeds() { Test.assertEqual(4, events.length) let cEvt = events[1] as! MigrationContractStaging.StagingStatusUpdated Test.assertEqual(bcAccount.address, cEvt.address) - Test.assertEqual(cUpdateCadence, cEvt.code) + Test.assertEqual(cUpdateHash, cEvt.code) Test.assertEqual("C", cEvt.contract) Test.assertEqual("stage", cEvt.action) let bEvt = events[2] as! MigrationContractStaging.StagingStatusUpdated Test.assertEqual(bcAccount.address, bEvt.address) - Test.assertEqual(bUpdateCadence, bEvt.code) + Test.assertEqual(bUpdateHash, bEvt.code) Test.assertEqual("B", bEvt.contract) Test.assertEqual("stage", bEvt.action) let aEvt = events[3] as! MigrationContractStaging.StagingStatusUpdated Test.assertEqual(aAccount.address, aEvt.address) - Test.assertEqual(aUpdateCadence, aEvt.code) + Test.assertEqual(aUpdateHash, aEvt.code) Test.assertEqual("A", aEvt.contract) Test.assertEqual("stage", aEvt.action) @@ -212,13 +225,22 @@ access(all) fun testStageMultipleContractsSucceeds() { let aStagedCode = getStagedContractCode(contractAddress: aAccount.address, contractName: "A") ?? panic("Problem retrieving result of getStagedContractCode()") + let aStagedHash = getStagedContractCodeHash(contractAddress: aAccount.address, contractName: "A") + ?? panic("Problem retrieving result of getStagedContractCodeHash()") let bStagedCode = getStagedContractCode(contractAddress: bcAccount.address, contractName: "B") ?? panic("Problem retrieving result of getStagedContractCode()") + let bStagedHash = getStagedContractCodeHash(contractAddress: bcAccount.address, contractName: "B") + ?? panic("Problem retrieving result of getStagedContractCodeHash()") let cStagedCode = getStagedContractCode(contractAddress: bcAccount.address, contractName: "C") ?? panic("Problem retrieving result of getStagedContractCode()") + let cStagedHash = getStagedContractCodeHash(contractAddress: bcAccount.address, contractName: "C") + ?? panic("Problem retrieving result of getStagedContractCodeHash()") Test.assertEqual(aUpdateCadence, aStagedCode) Test.assertEqual(bUpdateCadence, bStagedCode) Test.assertEqual(cUpdateCadence, cStagedCode) + Test.assertEqual(aUpdateHash, aStagedHash) + Test.assertEqual(bUpdateHash, bStagedHash) + Test.assertEqual(cUpdateHash, cStagedHash) let allStagedCodeForAAccount = getAllStagedContractCodeForAddress(contractAddress: aAccount.address) let allStagedCodeForBCAccount = getAllStagedContractCodeForAddress(contractAddress: bcAccount.address) @@ -238,7 +260,7 @@ access(all) fun testReplaceStagedCodeSucceeds() { Test.assertEqual(5, events.length) let evt = events[4] as! MigrationContractStaging.StagingStatusUpdated Test.assertEqual(fooAccount.address, evt.address) - Test.assertEqual(fooUpdateCadence, evt.code) + Test.assertEqual(fooUpdateHash, evt.code) Test.assertEqual("Foo", evt.contract) Test.assertEqual("replace", evt.action) } @@ -410,6 +432,14 @@ access(all) fun getStagedContractCode(contractAddress: Address, contractName: St return stagedContractCodeResult.returnValue as! String? } +access(all) fun getStagedContractCodeHash(contractAddress: Address, contractName: String): String? { + let stagedContractCodeResult = executeScript( + "../scripts/migration-contract-staging/get_staged_contract_code_hash.cdc", + [contractAddress, contractName] + ) + return stagedContractCodeResult.returnValue as! String? +} + access(all) fun getAllStagedContractCodeForAddress(contractAddress: Address): {String: String} { let allStagedContractCodeForAddressResult = executeScript( "../scripts/migration-contract-staging/get_all_staged_contract_code_for_address.cdc",