diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 3a63310..7531dd0 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -19,7 +19,13 @@ jobs: uses: actions/setup-go@v3 with: go-version: 1.18 + - name: Set up Node + uses: actions/setup-node@v2 + with: + node-version: ^18 + - name: Install Flow dependencies + run: npm i - name: Install Flow CLI - run: bash -ci "$(curl -fsSL https://raw.githubusercontent.com/onflow/flow-cli/master/install.sh)" -- v1.8.0 + run: bash -ci "$(curl -fsSL https://raw.githubusercontent.com/onflow/flow-cli/feature/stable-cadence/install.sh)" - name: Run tests run: sh ./run-tests.sh \ No newline at end of file diff --git a/contracts/FLOATRaffleSource.cdc b/contracts/FLOATRaffleSource.cdc index 297196b..238002a 100644 --- a/contracts/FLOATRaffleSource.cdc +++ b/contracts/FLOATRaffleSource.cdc @@ -4,41 +4,41 @@ import "FLOAT" /* FLOATRaffleSource - A Raffle source implementation which uses the claims on a FLOAT as the source of a drawing. */ -pub contract FLOATRaffleSource { - pub resource RaffleSource { - pub let eventCap: Capability<&FLOAT.FLOATEvents{FLOAT.FLOATEventsPublic}> - pub let eventId: UInt64 +access(all) contract FLOATRaffleSource { + access(all) resource RaffleSource { + access(all) let eventCap: Capability<&FLOAT.FLOATEvents> + access(all) let eventId: UInt64 - pub fun getEntries(): [AnyStruct] { + access(all) fun getEntries(): [AnyStruct] { return self.borrowEvent().getClaims().values } - pub fun getEntryCount(): Int { + access(all) fun getEntryCount(): Int { return self.borrowEvent().getClaims().length } - pub fun getEntryAt(index: Int): AnyStruct { + access(all) fun getEntryAt(index: Int): AnyStruct { return self.borrowEvent().getClaims()[UInt64(index)] } - pub fun revealCallback(drawingResult: FlowtyRaffles.DrawingResult) { + access(FlowtyRaffles.Reveal) fun revealCallback(drawingResult: FlowtyRaffles.DrawingResult) { return } - pub fun addEntry(_ v: AnyStruct) { + access(FlowtyRaffles.Add) fun addEntry(_ v: AnyStruct) { panic("addEntry is not supported on FLOATRaffleSource") } - pub fun addEntries(_ v: [AnyStruct]) { + access(FlowtyRaffles.Add) fun addEntries(_ v: [AnyStruct]) { panic("addEntries is not supported on FLOATRaffleSource") } - pub fun borrowEvent(): &FLOAT.FLOATEvent{FLOAT.FLOATEventPublic} { + access(all) fun borrowEvent(): &FLOAT.FLOATEvent { let cap = self.eventCap.borrow() ?? panic("eventCap is not valid") return cap.borrowPublicEventRef(eventId: self.eventId) ?? panic("invalid event id") } - init(eventCap: Capability<&FLOAT.FLOATEvents{FLOAT.FLOATEventsPublic}>, eventId: UInt64) { + init(eventCap: Capability<&FLOAT.FLOATEvents>, eventId: UInt64) { self.eventCap = eventCap self.eventId = eventId @@ -47,7 +47,7 @@ pub contract FLOATRaffleSource { } } - pub fun createRaffleSource(eventCap: Capability<&FLOAT.FLOATEvents{FLOAT.FLOATEventsPublic}>, eventId: UInt64): @RaffleSource { + access(all) fun createRaffleSource(eventCap: Capability<&FLOAT.FLOATEvents>, eventId: UInt64): @RaffleSource { return <- create RaffleSource(eventCap: eventCap, eventId: eventId) } } \ No newline at end of file diff --git a/contracts/FlowtyRaffleSource.cdc b/contracts/FlowtyRaffleSource.cdc index 2517d36..d6e8e66 100644 --- a/contracts/FlowtyRaffleSource.cdc +++ b/contracts/FlowtyRaffleSource.cdc @@ -13,29 +13,29 @@ In addition to entryType, a field called `removeAfterReveal` is also provided, w from the entries array any time a reveal is performed. This is useful for cases where you don't want the same entry to be able to be drawn multiple times. */ -pub contract FlowtyRaffleSource { - pub resource AnyStructRaffleSource: FlowtyRaffles.RaffleSourcePublic, FlowtyRaffles.RaffleSourcePrivate { - pub let entries: [AnyStruct] - pub let entryType: Type - pub let removeAfterReveal: Bool +access(all) contract FlowtyRaffleSource { + access(all) resource AnyStructRaffleSource: FlowtyRaffles.RaffleSourcePublic, FlowtyRaffles.RaffleSourcePrivate { + access(all) let entries: [AnyStruct] + access(all) let entryType: Type + access(all) let removeAfterReveal: Bool - pub fun getEntryType(): Type { + access(all) fun getEntryType(): Type { return self.entryType } - pub fun getEntryAt(index: Int): AnyStruct { + access(all) fun getEntryAt(index: Int): AnyStruct { return self.entries[index] } - pub fun getEntries(): [AnyStruct] { + access(all) fun getEntries(): [AnyStruct] { return self.entries } - pub fun getEntryCount(): Int { + access(all) fun getEntryCount(): Int { return self.entries.length } - pub fun addEntry(_ v: AnyStruct) { + access(FlowtyRaffles.Add) fun addEntry(_ v: AnyStruct) { pre { v.getType() == self.entryType: "incorrect entry type" } @@ -43,7 +43,7 @@ pub contract FlowtyRaffleSource { self.entries.append(v) } - pub fun addEntries(_ v: [AnyStruct]) { + access(FlowtyRaffles.Add) fun addEntries(_ v: [AnyStruct]) { pre { VariableSizedArrayType(self.entryType) == v.getType(): "incorrect array type" } @@ -51,7 +51,7 @@ pub contract FlowtyRaffleSource { self.entries.appendAll(v) } - pub fun revealCallback(drawingResult: FlowtyRaffles.DrawingResult) { + access(contract) fun revealCallback(drawingResult: FlowtyRaffles.DrawingResult) { if !self.removeAfterReveal { return } @@ -66,7 +66,7 @@ pub contract FlowtyRaffleSource { } } - pub fun createRaffleSource(entryType: Type, removeAfterReveal: Bool): @AnyStructRaffleSource { + access(all) fun createRaffleSource(entryType: Type, removeAfterReveal: Bool): @AnyStructRaffleSource { pre { entryType.isSubtype(of: Type()): "entry type must be a subtype of AnyStruct" } diff --git a/contracts/FlowtyRaffles.cdc b/contracts/FlowtyRaffles.cdc index ed5674e..524ca82 100644 --- a/contracts/FlowtyRaffles.cdc +++ b/contracts/FlowtyRaffles.cdc @@ -2,18 +2,22 @@ import "MetadataViews" import "RandomBeaconHistory" // import "Xorshift128plus" -pub contract FlowtyRaffles { - pub let ManagerStoragePath: StoragePath - pub let ManagerPublicPath: PublicPath +access(all) contract FlowtyRaffles { + access(all) let ManagerStoragePath: StoragePath + access(all) let ManagerPublicPath: PublicPath - pub event ManagerCreated(uuid: UInt64) - pub event RaffleCreated(address: Address?, raffleID: UInt64) - pub event RaffleReceiptCommitted(address: Address?, raffleID: UInt64, receiptID: UInt64, commitBlock: UInt64) - pub event RaffleReceiptRevealed(address: Address?, raffleID: UInt64, receiptID: UInt64, commitBlock: UInt64, revealHeight: UInt64, sourceType: Type, index: Int, value: String?, valueType: Type) + access(all) event ManagerCreated(uuid: UInt64) + access(all) event RaffleCreated(address: Address?, raffleID: UInt64) + access(all) event RaffleReceiptCommitted(address: Address?, raffleID: UInt64, receiptID: UInt64, commitBlock: UInt64) + access(all) event RaffleReceiptRevealed(address: Address?, raffleID: UInt64, receiptID: UInt64, commitBlock: UInt64, revealHeight: UInt64, sourceType: Type, index: Int, value: String?, valueType: Type) - pub struct DrawingResult { - pub let index: Int - pub let value: AnyStruct + access(all) entitlement Add + access(all) entitlement Reveal + access(all) entitlement Manage + + access(all) struct DrawingResult { + access(all) let index: Int + access(all) let value: AnyStruct init(_ index: Int, _ value: AnyStruct) { self.index = index @@ -34,62 +38,62 @@ pub contract FlowtyRaffles { there is not way for the raffle itself to know if a source is acting in good faith. Make sure you choose a raffle source implementation with care, as choosing one from an unknown party could result in unfair outcomes. */ - pub resource interface RaffleSourcePublic { + access(all) resource interface RaffleSourcePublic { /* getEntries - return all entries in this raffle. NOTE: This will not work if a raffle is so large that returning all its entries will exceed computation limits */ - pub fun getEntries(): [AnyStruct] + access(all) fun getEntries(): [AnyStruct] // getEntryCount - return the total number of entries in this raffle source - pub fun getEntryCount(): Int + access(all) fun getEntryCount(): Int // getEntryAt - return the entry at a specific index of a raffle source - pub fun getEntryAt(index: Int): AnyStruct + access(all) fun getEntryAt(index: Int): AnyStruct } - pub resource interface RaffleSourcePrivate { + access(all) resource interface RaffleSourcePrivate { /* revealCallback - a callback used when a raffle is revealed. This could be used to do things like remove an entry once it has been picked. As with similar notes above in the RaffleSourcePublic interface, make sure you trust the implementation of this callback so that it does not introduce unforeseen risk to your raffle */ - pub fun revealCallback(drawingResult: DrawingResult) + access(contract) fun revealCallback(drawingResult: DrawingResult) /* addEntries - adds an array of values into the raffle source, if it is permitted. NOTE: Some raffle source implementations might not permit this. As with other parts of this raffles implementation, be mindful of the source you are using and what it does */ - pub fun addEntries(_ v: [AnyStruct]) + access(Add) fun addEntries(_ v: [AnyStruct]) /* addEntry - adds value into the raffle source, if it is permitted. NOTE: Some raffle source implementations might not permit this. As with other parts of this raffles implementation, be mindful of the source you are using and what it does */ - pub fun addEntry(_ v: AnyStruct) + access(Add) fun addEntry(_ v: AnyStruct) } - pub resource interface RafflePublic { - pub fun borrowSourcePublic(): &{RaffleSourcePublic}? - pub fun getDetails(): Details + access(all) resource interface RafflePublic { + access(all) fun borrowSourcePublic(): &{RaffleSourcePublic}? + access(all) fun getDetails(): Details } - pub resource Receipt { + access(all) resource Receipt { // the block at which this receipt is allowed to be revealed - pub let commitBlock: UInt64 + access(all) let commitBlock: UInt64 // we record the uuid of the source used so that it cannot be swapped out // between stages - pub let sourceUuid: UInt64 + access(all) let sourceUuid: UInt64 // the block this receipt was revealed on - pub var revealBlock: UInt64? + access(all) var revealBlock: UInt64? // the result of this receipt once it has been reveald - pub var result: DrawingResult? + access(all) var result: DrawingResult? /* reveal - reveals the result of this receipt. A receipt can only be revealed if the current block is @@ -123,12 +127,12 @@ pub contract FlowtyRaffles { } } - pub struct Details { - pub let start: UInt64? - pub let end: UInt64? - pub let display: MetadataViews.Display? - pub let externalURL: MetadataViews.ExternalURL? - pub let commitBlocksAhead: UInt64 + access(all) struct Details { + access(all) let start: UInt64? + access(all) let end: UInt64? + access(all) let display: MetadataViews.Display? + access(all) let externalURL: MetadataViews.ExternalURL? + access(all) let commitBlocksAhead: UInt64 init(start: UInt64?, end: UInt64?, display: MetadataViews.Display?, externalURL: MetadataViews.ExternalURL?, commitBlocksAhead: UInt64) { self.start = start @@ -139,25 +143,25 @@ pub contract FlowtyRaffles { } } - pub resource Raffle: RafflePublic { + access(all) resource Raffle: RafflePublic { // Basic details about this raffle - pub let details: Details + access(all) let details: Details // a set of addresses which are allowed to perform reveals on a raffle. // set this to nil to allow anyone to reveal a drawing - pub var revealers: {Address: Bool}? + access(all) var revealers: {Address: Bool}? // The source of entries for this raffle. This allows a raffle to delegate out // what is being drawn. Some raffles might be for Addresses, others might be for // UInt64s or Strings - pub let source: @{RaffleSourcePublic, RaffleSourcePrivate} + access(contract) let source: @{RaffleSourcePublic, RaffleSourcePrivate} // Used to track all drawings done from this raffle. When a receipt is made, // it has no result until revealed, and can only be revealed if the current block is // equal to or greater than the block a receipt was made on + details.commitBlocksAhead - pub let receipts: @{UInt64: Receipt} + access(all) let receipts: @{UInt64: Receipt} - pub fun borrowSourcePublic(): &{RaffleSourcePublic}? { + access(all) fun borrowSourcePublic(): &{RaffleSourcePublic}? { return &self.source as &{RaffleSourcePublic, RaffleSourcePrivate} } @@ -190,7 +194,7 @@ pub contract FlowtyRaffles { return res } - pub fun addEntries(_ v: [AnyStruct]) { + access(Add) fun addEntries(_ v: [AnyStruct]) { let blockTs = UInt64(getCurrentBlock().timestamp) assert(self.details.start == nil || self.details.start! <= blockTs, message: "cannot add entries to a raffle that has not started") @@ -199,7 +203,7 @@ pub contract FlowtyRaffles { self.source.addEntries(v) } - pub fun addEntry(_ v: AnyStruct) { + access(Add) fun addEntry(_ v: AnyStruct) { let blockTs = UInt64(getCurrentBlock().timestamp) assert(self.details.start == nil || self.details.start! <= blockTs, message: "cannot add entries to a raffle that has not started") @@ -208,7 +212,7 @@ pub contract FlowtyRaffles { self.source.addEntry(v) } - pub fun getDetails(): Details { + access(all) fun getDetails(): Details { return self.details } @@ -234,11 +238,6 @@ pub contract FlowtyRaffles { } } } - - destroy () { - destroy self.source - destroy self.receipts - } } /* @@ -249,9 +248,9 @@ pub contract FlowtyRaffles { In addition to getting a raffle, the manager public also provides a way to reveal a raffle's outcome which is available for anyone to do based on the commit-reveal scheme underneath the raffle's management itself. */ - pub resource interface ManagerPublic { - pub fun borrowRafflePublic(id: UInt64): &{RafflePublic}? - pub fun revealDrawing(manager: &Manager{ManagerPublic}, raffleID: UInt64, receiptID: UInt64) + access(all) resource interface ManagerPublic { + access(all) fun borrowRafflePublic(id: UInt64): &{RafflePublic}? + access(all) fun revealDrawing(manager: &Manager, raffleID: UInt64, receiptID: UInt64) access(contract) fun _revealDrawing(raffleID: UInt64, receiptID: UInt64, drawer: &Manager) } @@ -260,28 +259,24 @@ pub contract FlowtyRaffles { This is made into its own interface so that a manager can be delegated out to others in the event that community run raffles are desired. One could make a capability to the private manager and share it with others they trust. */ - pub resource interface ManagerPrivate { - pub fun borrowRaffle(id: UInt64): &Raffle? - pub fun commitDrawing(raffleID: UInt64): UInt64 + access(all) resource interface ManagerPrivate { + access(Manage) fun borrowRaffle(id: UInt64): auth(Add) &Raffle? + access(Manage) fun commitDrawing(raffleID: UInt64): UInt64 } // This is an empty interface to give reveal requests the ability to vet whether or not // the calling manager is permitted to perform a reveal on a given drawing or not - pub resource interface ManagerIdentity {} + access(all) resource interface ManagerIdentity {} - pub resource Manager: ManagerPublic, ManagerPrivate { - pub let raffles: @{UInt64: Raffle} + access(all) resource Manager: ManagerPublic, ManagerPrivate { + access(self) let raffles: @{UInt64: Raffle} - pub fun borrowRafflePublic(id: UInt64): &{RafflePublic}? { + access(all) fun borrowRafflePublic(id: UInt64): &{RafflePublic}? { return self.borrowRaffle(id: id) } - pub fun borrowRaffle(id: UInt64): &Raffle? { - if self.raffles[id] == nil { - return nil - } - - return &self.raffles[id] as &Raffle? + access(Manage) fun borrowRaffle(id: UInt64): auth(Add) &Raffle? { + return &self.raffles[id] } /* @@ -291,7 +286,7 @@ pub contract FlowtyRaffles { 3. how many blocks ahead a result must be to be revealed. Setting commitBlocksAhead to 0 means a reveal can be done in the same block as the commit, and is subject to reversion risks discussed here: https://developers.flow.com/build/advanced-concepts/randomness#guidelines-for-safe-usage */ - pub fun createRaffle(source: @{RaffleSourcePublic, RaffleSourcePrivate}, details: Details, revealers: [Address]?): UInt64 { + access(Manage) fun createRaffle(source: @{RaffleSourcePublic, RaffleSourcePrivate}, details: Details, revealers: [Address]?): UInt64 { let raffle <- create Raffle(source: <-source, details: details, revealers: revealers) let uuid = raffle.uuid emit RaffleCreated(address: self.owner?.address, raffleID: uuid) @@ -304,7 +299,7 @@ pub contract FlowtyRaffles { commitDrawing - commits a new drawing for a raffle, creating a receipt resource with the specified commit block height to be revealed at a later date. */ - pub fun commitDrawing(raffleID: UInt64): UInt64 { + access(Manage) fun commitDrawing(raffleID: UInt64): UInt64 { let raffle = self.borrowRaffle(id: raffleID) ?? panic("raffle not found") @@ -322,7 +317,7 @@ pub contract FlowtyRaffles { revealDrawing - reveals the result of a drawing, taking the committed data in the commit stage and using it to generate a random number to draw an entry from our raffle source */ - pub fun revealDrawing(manager: &Manager{ManagerPublic}, raffleID: UInt64, receiptID: UInt64) { + access(all) fun revealDrawing(manager: &Manager, raffleID: UInt64, receiptID: UInt64) { let ref = &self as &Manager manager._revealDrawing(raffleID: raffleID, receiptID: receiptID, drawer: ref) } @@ -333,7 +328,7 @@ pub contract FlowtyRaffles { assert(raffle.revealers == nil || raffle.revealers![drawer.owner!.address] == true, message: "drawer is not permitted to perform reveals on this raffle") let drawingResult = raffle.revealDrawing(id: receiptID) - let receipt = (&raffle.receipts[receiptID] as &Receipt?)! + let receipt = (raffle.receipts[receiptID])! var v = FlowtyRaffles.extractString(drawingResult.value) emit RaffleReceiptRevealed(address: self.owner?.address, raffleID: raffleID, receiptID: receiptID, commitBlock: receipt.commitBlock, revealHeight: getCurrentBlock().height, sourceType: raffle.source.getType(), index: drawingResult.index, value: v, valueType: drawingResult.value.getType()) @@ -342,15 +337,11 @@ pub contract FlowtyRaffles { init() { self.raffles <- {} } - - destroy () { - destroy self.raffles - } } // taken from // https://github.com/onflow/random-coin-toss/blob/4271cd571b7761af36b0f1037767171aeca18387/contracts/CoinToss.cdc#L95 - pub fun randUInt64(atBlockHeight: UInt64, salt: UInt64): UInt64 { + access(all) fun randUInt64(atBlockHeight: UInt64, salt: UInt64): UInt64 { // // query the Random Beacon history core-contract - if `blockHeight` <= current block height, panic & revert // let sourceOfRandomness = RandomBeaconHistory.sourceOfRandomness(atBlockHeight: atBlockHeight) // assert(sourceOfRandomness.blockHeight == atBlockHeight, message: "RandomSource block height mismatch") @@ -364,10 +355,10 @@ pub contract FlowtyRaffles { // return prg.nextUInt64() // TODO: use commented-out implementation once we can test using the randomness beacon in the cadence testing framework - return revertibleRandom() + return revertibleRandom() } - pub fun extractString(_ value: AnyStruct?): String? { + access(all) fun extractString(_ value: AnyStruct?): String? { if value == nil { return nil } @@ -392,7 +383,7 @@ pub contract FlowtyRaffles { return nil } - pub fun createManager(): @Manager { + access(all) fun createManager(): @Manager { return <- create Manager() } diff --git a/contracts/standard/RandomBeaconHistory.cdc b/contracts/standard/RandomBeaconHistory.cdc index 28d20ab..0458526 100644 --- a/contracts/standard/RandomBeaconHistory.cdc +++ b/contracts/standard/RandomBeaconHistory.cdc @@ -3,16 +3,16 @@ /// This contract stores the history of random sources generated by the Flow network. The defined Heartbeat resource is /// updated by the Flow Service Account at the end of every block with that block's source of randomness. /// -/// While the source values are safely generated by the Random Beacon (non-predictable, unbiasable, verifiable) and transmitted into the execution -/// environment via the committing transaction, using the raw values from this contract does not guarantee non-revertible -/// randomness. The Hearbeat is intended to be used in conjunction with a -/// commit-reveal mechanism to provide an onchain source of non-revertible randomness. -// It is also recommended to use the source values with a pseudo-random number -// generator (PRNG) to generate an arbitrary-long sequence of random values. -// -// For usage of randomness where result abortion is not an issue, it is recommended -// to use the Cadence built-in function `revertibleRandom`, which is also based on -// the safe Random Beacon. +/// While the source values are safely generated by the Random Beacon (non-predictable, unbiasable, verifiable) and +/// transmitted into the execution environment via the committing transaction, using the raw values from this contract +/// does not guarantee non-revertible randomness. The Hearbeat is intended to be used in conjunction with a commit-reveal +/// mechanism to provide an onchain source of non-revertible randomness. +/// It is also recommended to use the source values with a pseudo-random number +/// generator (PRNG) to generate an arbitrary-long sequence of random values. +/// +/// For usage of randomness where result abortion is not an issue, it is recommended +/// to use the Cadence built-in function `revertibleRandom`, which is also based on +/// the safe Random Beacon. /// /// Read the full FLIP here: https://github.com/onflow/flips/pull/123 /// @@ -26,6 +26,19 @@ access(all) contract RandomBeaconHistory { /// The path of the Heartbeat resource in the deployment account access(all) let HeartbeatStoragePath: StoragePath + // Event emitted when missing SoRs from past heartbeats are detected and will be backfilled: + // - `blockHeight` is the height where the gap is detected + // - `gapStartHeight` is the height of the first missing entry detected + access(all) event RandomHistoryMissing(blockHeight: UInt64, gapStartHeight: UInt64) + + // Event emitted when missing SoRs are backfilled on the current heartbeat: + // - `blockHeight` is the height where the backfill happened, it also defines the SoR used to backfill + // - `gapStartHeight` is the height of the first backfilled entry + // - `count` is the number of backfilled entries + // Note that in very rare cases, the backfilled gap may not be contiguous. This event does not + // fully define the backfilled entries in this case. + access(all) event RandomHistoryBackfilled(blockHeight: UInt64, gapStartHeight: UInt64, count: UInt64) + /* --- Hearbeat --- */ // /// The Heartbeat resource containing each block's source of randomness in sequence @@ -36,17 +49,166 @@ access(all) contract RandomBeaconHistory { /// /// @param randomSourceHistory The random source to record /// + /// The Flow protocol makes sure to call this function once per block as a system call. The transaction + /// comes at the end of each block so that the current block's entry becomes available only in the child + /// block. + /// access(all) fun heartbeat(randomSourceHistory: [UInt8]) { + assert( + // random source must be at least 128 bits + randomSourceHistory.length >= 128 / 8, + message: "Random source must be at least 128 bits" + ) + let currentBlockHeight = getCurrentBlock().height + // init lowestBlockHeight if it is not set yet if RandomBeaconHistory.lowestHeight == nil { RandomBeaconHistory.lowestHeight = currentBlockHeight } + var maybeBackfiller = RandomBeaconHistory.borrowBackfiller() + // Create & save Backfiller if it is not saved yet + if maybeBackfiller == nil { + RandomBeaconHistory.account.storage.save(<-create Backfiller(), to: /storage/randomBeaconHistoryBackfiller) + maybeBackfiller = RandomBeaconHistory.borrowBackfiller() + } + let backfiller = maybeBackfiller ?? panic("Problem borrowing backfiller") + + // check for any existing gap and backfill using the input random source if needed. + backfiller.backfill(randomSource: randomSourceHistory) + // we are now at the correct index to record the source of randomness + // created by the protocol for the current block RandomBeaconHistory.randomSourceHistory.append(randomSourceHistory) } } + /* --- Backfiller --- */ + // + /// A recovery mechanism designed to backfill missed sources of randomness in the event of a missed commitment due + /// to a system transaction failure. + /// + access(all) resource Backfiller { + /// Start index of the first gap in the `randomSourceHistory` array where random sources were not recorded, + /// because of a heartbeat failure. + /// There may be non contiguous gaps in the history, `gapStartIndex` is the start index of the lowest-height + /// gap. + /// If no gaps exist, `gapStartIndex` is equal to the `randomSourceHistory` array length. + access(contract) var gapStartIndex: UInt64 + /// BackFilling is limited to a maximum number of entries per call to limit the computation cost. + /// This means a large gap may need a few calls to get fully backfilled. + access(contract) var maxEntriesPerCall: UInt64 + + init() { + self.gapStartIndex = UInt64(RandomBeaconHistory.randomSourceHistory.length) + self.maxEntriesPerCall = 100 + } + + access(all) view fun getMaxEntriesPerCall() : UInt64 { + return self.maxEntriesPerCall + } + + access(all) fun setMaxEntriesPerCall(max: UInt64) { + assert( + max > 0, + message: "the maximum entry per call must be strictly positive" + ) + self.maxEntriesPerCall = max + } + + /// Finds the correct index to fill with the new random source. If a gap is detected, emits the + /// RandomHistoryMissing event. + /// + access(contract) view fun findGapAndReturnCorrectIndex(): UInt64 { + + let currentBlockHeight = getCurrentBlock().height + // correct index to fill with the new random source + // so that eventually randomSourceHistory[correctIndex] = inputRandom + let lowestHeight = RandomBeaconHistory.lowestHeight! + let correctIndex = currentBlockHeight - lowestHeight + + // if a new gap is detected, emit an event + var arrayLength = UInt64(RandomBeaconHistory.randomSourceHistory.length) + if correctIndex > arrayLength { + let gapStartHeight = lowestHeight + arrayLength + emit RandomHistoryMissing(blockHeight: currentBlockHeight, gapStartHeight: gapStartHeight) + } + return correctIndex + } + + /// Backfills possible empty entries (gaps) in the history array starting from the stored `gapStartIndex`, + /// using `randomSource` as a seed for all entries. + /// If there are no gaps, `gapStartIndex` is just updated to `RandomBeaconHistory`'s length. + // + /// When backfilling, all entries use the same entropy. Each entry is extracted from `randomSource` using + /// successive hashing. This makes sure the entries are all distinct although they provide + /// the same entropy. + // + /// gaps only occur in the rare event of a system transaction failure. In this case, entries are still + /// filled using a source not known at the time of block execution, which guarantees unpredicatability. + access(contract) fun backfill(randomSource: [UInt8]) { + + let correctIndex = self.findGapAndReturnCorrectIndex() + var arrayLength = UInt64(RandomBeaconHistory.randomSourceHistory.length) + // optional optimization for the happy common path: if no gaps are detected, + // backfilling isn't needed, return early + if correctIndex == self.gapStartIndex { + self.gapStartIndex = arrayLength + 1 + return + } + + // If a new gap is detected in the current transaction, fill the gap with empty entries. + // This happens in the rare case where a new gap occurs because of a system transaction failure. + while correctIndex > UInt64(RandomBeaconHistory.randomSourceHistory.length) { + RandomBeaconHistory.randomSourceHistory.append([]) + } + + arrayLength = UInt64(RandomBeaconHistory.randomSourceHistory.length) + var newEntry = randomSource + var index = self.gapStartIndex + var count = 0 as UInt64 + while count < self.maxEntriesPerCall { + // move to the next empty entry + while index < arrayLength && RandomBeaconHistory.randomSourceHistory[index].length > 0 { + index = index + 1 + } + // if we reach the end of the array then all existing gaps got filled + if index == arrayLength { + break + } + // back fill the empty entry + // It is guaranteed that sha3 output (256 bits) is larger than the minimum + // required size of an SoR (128 bits) + newEntry = HashAlgorithm.SHA3_256.hash(newEntry) + RandomBeaconHistory.randomSourceHistory[index] = newEntry + index = index + 1 + count = count + 1 + } + + // emit an event about backfilled entries + if count > 0 { + let gapStartHeight = RandomBeaconHistory.lowestHeight! + self.gapStartIndex + emit RandomHistoryBackfilled( + blockHeight: getCurrentBlock().height, + gapStartHeight: gapStartHeight, + count: count + ) + } + + // no more backfilling is possible but we need to update `gapStartIndex` + // to: + // - the next empty index if gaps still exist + // - the length of the array at the end of the transaction if there are no gaps + while index < arrayLength && RandomBeaconHistory.randomSourceHistory[index].length > 0 { + index = index + 1 + } + if index == arrayLength { + index = index + 1 // take into account the upcoming append of the SoR at the correct index + } + self.gapStartIndex = index + } + } + /* --- RandomSourceHistory --- */ // /// Represents a random source value for a given block height @@ -54,7 +216,7 @@ access(all) contract RandomBeaconHistory { access(all) struct RandomSource { access(all) let blockHeight: UInt64 access(all) let value: [UInt8] - + init(blockHeight: UInt64, value: [UInt8]) { self.blockHeight = blockHeight self.value = value @@ -71,7 +233,7 @@ access(all) contract RandomBeaconHistory { access(all) let totalLength: UInt64 access(all) let values: [RandomSource] - init(page: UInt64, perPage: UInt64, totalLength: UInt64, values: [RandomSource]) { + view init(page: UInt64, perPage: UInt64, totalLength: UInt64, values: [RandomSource]) { self.page = page self.perPage = perPage self.totalLength = totalLength @@ -89,21 +251,25 @@ access(all) contract RandomBeaconHistory { /// /// @return The source of randomness at the given block height as RandomSource struct /// - access(all) fun sourceOfRandomness(atBlockHeight: UInt64): RandomSource { + access(all) fun sourceOfRandomness(atBlockHeight blockHeight: UInt64): RandomSource { pre { self.lowestHeight != nil: "History has not yet been initialized" - atBlockHeight >= self.lowestHeight!: "Requested block height precedes recorded history" - atBlockHeight < getCurrentBlock().height: "Source of randomness not yet recorded" + blockHeight >= self.lowestHeight!: "Requested block height precedes recorded history" + blockHeight < getCurrentBlock().height: "Source of randomness not yet recorded" } - let index: UInt64 = atBlockHeight - self.lowestHeight! + let index = blockHeight - self.lowestHeight! assert( - index >= 0 && index < UInt64(self.randomSourceHistory.length), + index >= 0, message: "Problem finding random source history index" ) - return RandomSource(blockHeight: atBlockHeight, value: self.randomSourceHistory[index]) + assert( + index < UInt64(self.randomSourceHistory.length) && self.randomSourceHistory[index].length > 0, + message: "Source of randomness is currently not available but will be available soon" + ) + return RandomSource(blockHeight: blockHeight, value: self.randomSourceHistory[index]) } - /// Retrieves a page from the history of random sources, ordered chronologically + /// Retrieves a page from the history of random sources recorded so far, ordered chronologically /// /// @param page: The page number to retrieve, 0-indexed /// @param perPage: The number of random sources to include per page @@ -111,7 +277,7 @@ access(all) contract RandomBeaconHistory { /// @return A RandomSourceHistoryPage containing RandomSource values in choronological order according to /// associated block height /// - access(all) view fun getRandomSourceHistoryPage(page: UInt64, perPage: UInt64): RandomSourceHistoryPage { + access(all) fun getRandomSourceHistoryPage(_ page: UInt64, perPage: UInt64): RandomSourceHistoryPage { pre { self.lowestHeight != nil: "History has not yet been initialized" } @@ -126,17 +292,23 @@ access(all) contract RandomBeaconHistory { if endIndex > totalLength { endIndex = totalLength } + // Return empty page if request exceeds last page if startIndex == endIndex { return RandomSourceHistoryPage(page: page, perPage: perPage, totalLength: totalLength, values: values) } // Iterate over history and construct page RandomSource values - for i, block in self.randomSourceHistory.slice(from: Int(startIndex), upTo: Int(endIndex)) { + let lowestHeight = self.lowestHeight! + for i, value in self.randomSourceHistory.slice(from: Int(startIndex), upTo: Int(endIndex)) { + assert( + value.length > 0, + message: "Source of randomness is currently not available but will be available soon" + ) values.append( RandomSource( - blockHeight: self.lowestHeight! + startIndex + UInt64(i), - value: self.randomSourceHistory[startIndex + UInt64(i)] + blockHeight: lowestHeight + startIndex + UInt64(i), + value: value ) ) } @@ -157,11 +329,16 @@ access(all) contract RandomBeaconHistory { return self.lowestHeight ?? panic("History has not yet been initialized") } + /// Getter for the contract's Backfiller resource + access(contract) fun borrowBackfiller(): &Backfiller? { + return self.account.storage.borrow<&Backfiller>(from: /storage/randomBeaconHistoryBackfiller) + } + init() { self.lowestHeight = nil self.randomSourceHistory = [] self.HeartbeatStoragePath = /storage/FlowRandomBeaconHistoryHeartbeat - self.account.save(<-create Heartbeat(), to: self.HeartbeatStoragePath) + self.account.storage.save(<-create Heartbeat(), to: self.HeartbeatStoragePath) } } \ No newline at end of file diff --git a/flow.json b/flow.json index 718a6b0..f324880 100644 --- a/flow.json +++ b/flow.json @@ -54,24 +54,24 @@ "mainnet": "0x2fb4614ede95ab2b" } }, - "RandomBeaconHistory": { - "source": "./contracts/standard/RandomBeaconHistory.cdc", - "aliases": { - "emulator": "0xf8d6e0586b0a20c7", - "mainnet": "0xe467b9dd11fa00df", - "testnet": "0x8c5303eaa26202d6", + "RandomBeaconHistory": { + "source": "./contracts/standard/RandomBeaconHistory.cdc", + "aliases": { + "emulator": "0xf8d6e0586b0a20c7", + "mainnet": "0xe467b9dd11fa00df", + "testnet": "0x8c5303eaa26202d6", "testing": "0x0000000000000001" - } - }, - "Xorshift128plus": { - "source": "./contracts/standard/Xorshift128plus.cdc", - "aliases": { - "emulator": "0xf8d6e0586b0a20c7", - "testnet": "0xed24dbe901028c5c", - "mainnet": "0x45caec600164c9e6", + } + }, + "Xorshift128plus": { + "source": "./contracts/standard/Xorshift128plus.cdc", + "aliases": { + "emulator": "0xf8d6e0586b0a20c7", + "testnet": "0xed24dbe901028c5c", + "mainnet": "0x45caec600164c9e6", "testing": "0x0000000000000009" - } - }, + } + }, "FungibleToken": { "source": "./node_modules/@flowtyio/flow-contracts/contracts/FungibleToken.cdc", "aliases": { @@ -137,6 +137,14 @@ "testnet": "0x4d47bf3ce5e4393f", "testing": "0x0000000000000005" } + }, + "Burner": { + "source": "./node_modules/@flowtyio/flow-contracts/contracts/Burner.cdc", + "aliases": { + "emulator": "0xf8d6e0586b0a20c7", + "testnet": "0x9a0766d93b6608b7", + "mainnet": "0xf233dcee88fe0abe" + } } }, "deployments": { @@ -147,7 +155,9 @@ "NonFungibleToken", "RandomBeaconHistory", "Xorshift128plus", - "FlowtyRaffles" + "FlowtyRaffles", + "FlowtyRaffleSource", + "Burner" ], "emulator-ft": [ "FungibleToken", diff --git a/package-lock.json b/package-lock.json index 7bdf8c8..76f5be1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,13 +5,13 @@ "packages": { "": { "dependencies": { - "@flowtyio/flow-contracts": "^0.0.18" + "@flowtyio/flow-contracts": "^0.1.0-beta.23" } }, "node_modules/@flowtyio/flow-contracts": { - "version": "0.0.18", - "resolved": "https://registry.npmjs.org/@flowtyio/flow-contracts/-/flow-contracts-0.0.18.tgz", - "integrity": "sha512-uQVbUOZegx8sdk/T+ypYBjdVmcw7AGHANzrg+PdQCtYXBGJ3iEh9+vxwf2kNk2j6hw8djmWdZy36iUMZC3pwmg==", + "version": "0.1.0-beta.23", + "resolved": "https://registry.npmjs.org/@flowtyio/flow-contracts/-/flow-contracts-0.1.0-beta.23.tgz", + "integrity": "sha512-UJHC8Qm5oYfOr7Bt+3I5KFPZgKVUVwdTlsUgkCLHW7Xo4o/w4KHMuyw4vHX0oLT8twYhU9bOUac/2p/rJDZe1w==", "dependencies": { "commander": "^11.0.0" }, @@ -30,9 +30,9 @@ }, "dependencies": { "@flowtyio/flow-contracts": { - "version": "0.0.18", - "resolved": "https://registry.npmjs.org/@flowtyio/flow-contracts/-/flow-contracts-0.0.18.tgz", - "integrity": "sha512-uQVbUOZegx8sdk/T+ypYBjdVmcw7AGHANzrg+PdQCtYXBGJ3iEh9+vxwf2kNk2j6hw8djmWdZy36iUMZC3pwmg==", + "version": "0.1.0-beta.23", + "resolved": "https://registry.npmjs.org/@flowtyio/flow-contracts/-/flow-contracts-0.1.0-beta.23.tgz", + "integrity": "sha512-UJHC8Qm5oYfOr7Bt+3I5KFPZgKVUVwdTlsUgkCLHW7Xo4o/w4KHMuyw4vHX0oLT8twYhU9bOUac/2p/rJDZe1w==", "requires": { "commander": "^11.0.0" } diff --git a/package.json b/package.json index 790bde2..b98d3a6 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { "dependencies": { - "@flowtyio/flow-contracts": "^0.0.18" + "@flowtyio/flow-contracts": "^0.1.0-beta.23" } } diff --git a/run-tests.sh b/run-tests.sh index acc7741..03faee0 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -2,4 +2,4 @@ set -e -flow test --cover --covercode="contracts" --coverprofile="coverage.lcov" test/*_tests.cdc \ No newline at end of file +flow-c1 test --cover --covercode="contracts" --coverprofile="coverage.lcov" test/*_tests.cdc \ No newline at end of file diff --git a/scripts/borrow_manager.cdc b/scripts/borrow_manager.cdc index 43b013a..759973b 100644 --- a/scripts/borrow_manager.cdc +++ b/scripts/borrow_manager.cdc @@ -1,6 +1,6 @@ import "FlowtyRaffles" -pub fun main(addr: Address) { - getAccount(addr).getCapability<&FlowtyRaffles.Manager{FlowtyRaffles.ManagerPublic}>(FlowtyRaffles.ManagerPublicPath).borrow() +access(all) fun main(addr: Address) { + getAccount(addr).capabilities.get<&{FlowtyRaffles.ManagerPublic}>(FlowtyRaffles.ManagerPublicPath).borrow() ?? panic("unable to borrow manager") } \ No newline at end of file diff --git a/scripts/get_num_raffle_entries.cdc b/scripts/get_num_raffle_entries.cdc index 86ee2cc..bb3ad5a 100644 --- a/scripts/get_num_raffle_entries.cdc +++ b/scripts/get_num_raffle_entries.cdc @@ -1,8 +1,8 @@ import "FlowtyRaffles" -pub fun main(addr: Address, id: UInt64): Int { - let acct = getAuthAccount(addr) - let manager = acct.borrow<&FlowtyRaffles.Manager{FlowtyRaffles.ManagerPublic}>(from: FlowtyRaffles.ManagerStoragePath) +access(all) fun main(addr: Address, id: UInt64): Int { + let acct = getAuthAccount(addr) + let manager = acct.storage.borrow<&{FlowtyRaffles.ManagerPublic}>(from: FlowtyRaffles.ManagerStoragePath) ?? panic("raffles manager not found") let raffle = manager.borrowRafflePublic(id: id) ?? panic("raffle not found") diff --git a/scripts/get_raffle_details.cdc b/scripts/get_raffle_details.cdc index 43eccb9..55a9968 100644 --- a/scripts/get_raffle_details.cdc +++ b/scripts/get_raffle_details.cdc @@ -1,8 +1,8 @@ import "FlowtyRaffles" -pub fun main(addr: Address, id: UInt64): FlowtyRaffles.Details? { - let acct = getAuthAccount(addr) - let manager = acct.borrow<&FlowtyRaffles.Manager>(from: FlowtyRaffles.ManagerStoragePath) +access(all) fun main(addr: Address, id: UInt64): FlowtyRaffles.Details? { + let acct = getAuthAccount(addr) + let manager = acct.storage.borrow<&FlowtyRaffles.Manager>(from: FlowtyRaffles.ManagerStoragePath) ?? panic("raffles manager not found") if let raffle = manager.borrowRafflePublic(id: id) { diff --git a/scripts/get_raffle_entries.cdc b/scripts/get_raffle_entries.cdc index 4d1e976..d021cf7 100644 --- a/scripts/get_raffle_entries.cdc +++ b/scripts/get_raffle_entries.cdc @@ -1,8 +1,8 @@ import "FlowtyRaffles" -pub fun main(addr: Address, id: UInt64): [AnyStruct] { - let acct = getAuthAccount(addr) - let manager = acct.borrow<&FlowtyRaffles.Manager{FlowtyRaffles.ManagerPublic}>(from: FlowtyRaffles.ManagerStoragePath) +access(all) fun main(addr: Address, id: UInt64): [AnyStruct] { + let acct = getAuthAccount(addr) + let manager = acct.storage.borrow<&{FlowtyRaffles.ManagerPublic}>(from: FlowtyRaffles.ManagerStoragePath) ?? panic("raffles manager not found") let raffle = manager.borrowRafflePublic(id: id) ?? panic("raffle not found") diff --git a/scripts/get_raffle_source_identifier.cdc b/scripts/get_raffle_source_identifier.cdc index b3be1db..e5700b9 100644 --- a/scripts/get_raffle_source_identifier.cdc +++ b/scripts/get_raffle_source_identifier.cdc @@ -1,7 +1,7 @@ import "FlowtyRaffles" -pub fun main(addr: Address, path: StoragePath): String { - let acct = getAuthAccount(addr) - let source = acct.borrow<&{FlowtyRaffles.RaffleSourcePublic, FlowtyRaffles.RaffleSourcePrivate}>(from: path) +access(all) fun main(addr: Address, path: StoragePath): String { + let acct = getAuthAccount(addr) + let source = acct.storage.borrow<&{FlowtyRaffles.RaffleSourcePublic, FlowtyRaffles.RaffleSourcePrivate}>(from: path) return source!.getType().identifier } \ No newline at end of file diff --git a/test/FlowtyRaffles_tests.cdc b/test/FlowtyRaffles_tests.cdc index d743019..a3f1026 100644 --- a/test/FlowtyRaffles_tests.cdc +++ b/test/FlowtyRaffles_tests.cdc @@ -2,16 +2,15 @@ import Test import "test_helpers.cdc" import "FlowtyRaffles" -import "FlowtyRaffleSource" import "MetadataViews" -pub let FlowtyRafflesContractAddress = Address(0x0000000000000007) -pub let FlowtyRaffleSourceContractAddress = Address(0x0000000000000008) -pub let Xorshift128plusContractAddress = Address(0x0000000000000008) +access(all) let FlowtyRafflesContractAddress = Address(0x0000000000000007) +access(all) let FlowtyRaffleSourceContractAddress = Address(0x0000000000000008) +access(all) let Xorshift128plusContractAddress = Address(0x0000000000000008) -pub let GenericRaffleSourceIdentifier = "A.0000000000000008.FlowtyRaffleSource.AnyStructRaffleSource" +access(all) let GenericRaffleSourceIdentifier = "A.0000000000000008.FlowtyRaffleSource.AnyStructRaffleSource" -pub fun setup() { +access(all) fun setup() { var err = Test.deployContract(name: "FlowtyRaffles", path: "../contracts/FlowtyRaffles.cdc", arguments: []) Test.expect(err, Test.beNil()) @@ -22,13 +21,13 @@ pub fun setup() { // Test.expect(err, Test.beNil()) } -pub fun testSetupManager() { +access(all) fun testSetupManager() { let acct = Test.createAccount() txExecutor("setup_manager.cdc", [acct], [], nil) scriptExecutor("borrow_manager.cdc", [acct.address]) } -pub fun testCreateRaffleSource() { +access(all) fun testCreateRaffleSource() { let acct = Test.createAccount() let path = StoragePath(identifier: "testCreateRaffleSource")! txExecutor("create_raffle_source.cdc", [acct], [Type
(), path], nil) @@ -36,7 +35,7 @@ pub fun testCreateRaffleSource() { assert(res! as! String == GenericRaffleSourceIdentifier, message: "unexpected raffle source identifier") } -pub fun testCreateRaffle() { +access(all) fun testCreateRaffle() { let acct = Test.createAccount() let name = "testCreateRaffle" let description = "testCreateRaffle desc" @@ -59,7 +58,7 @@ pub fun testCreateRaffle() { assert(end == details.end) } -pub fun testAddToRaffle() { +access(all) fun testAddToRaffle() { let acct = Test.createAccount() let id = createAddressRaffle(acct) let beforeEntries = getRaffleEntries(acct, id)! @@ -73,7 +72,7 @@ pub fun testAddToRaffle() { assert(afterEntries[0] as! Address == acct.address) } -pub fun testDrawFromRaffle() { +access(all) fun testDrawFromRaffle() { let acct = Test.createAccount() let id = createAddressRaffle(acct) let beforeEntries = getRaffleEntries(acct, id)! @@ -84,7 +83,7 @@ pub fun testDrawFromRaffle() { assert(drawing == acct.address.toString()) // now let's add lots of additional entries - let accounts: {String: Test.Account} = { + let accounts: {String: Test.TestAccount} = { acct.address.toString(): acct } var count = 0 @@ -99,36 +98,36 @@ pub fun testDrawFromRaffle() { assert(accounts[drawing2] != nil) } -pub fun getRaffleDetails(_ acct: Test.Account, _ id: UInt64): FlowtyRaffles.Details? { +access(all) fun getRaffleDetails(_ acct: Test.TestAccount, _ id: UInt64): FlowtyRaffles.Details? { if let res = scriptExecutor("get_raffle_details.cdc", [acct.address, id]) { return res as! FlowtyRaffles.Details } return nil } -pub fun getRaffleEntries(_ acct: Test.Account, _ id: UInt64): [AnyStruct]? { +access(all) fun getRaffleEntries(_ acct: Test.TestAccount, _ id: UInt64): [AnyStruct]? { if let res = scriptExecutor("get_raffle_entries.cdc", [acct.address, id]) { return res as! [AnyStruct] } return nil } -pub fun getRaffleEntriesCount(_ acct: Test.Account, _ id: UInt64): Int? { +access(all) fun getRaffleEntriesCount(_ acct: Test.TestAccount, _ id: UInt64): Int? { if let res = scriptExecutor("get_num_raffle_entries.cdc", [acct.address, id]) { return res as! Int } return nil } -pub fun addEntryToRaffle(_ acct: Test.Account, _ id: UInt64, _ entry: AnyStruct) { +access(all) fun addEntryToRaffle(_ acct: Test.TestAccount, _ id: UInt64, _ entry: AnyStruct) { txExecutor("add_entry_to_raffle.cdc", [acct], [id, entry], nil) } -pub fun addEntriesToRaffle(_ acct: Test.Account, _ id: UInt64, _ entries: [AnyStruct]) { +access(all) fun addEntriesToRaffle(_ acct: Test.TestAccount, _ id: UInt64, _ entries: [AnyStruct]) { txExecutor("add_entries_to_raffle.cdc", [acct], [id, entries], nil) } -pub fun createAddressRaffle(_ acct: Test.Account): UInt64 { +access(all) fun createAddressRaffle(_ acct: Test.TestAccount): UInt64 { let name = "address raffle" let description = "address raffle desc" let thumbnail = "https://example.com/thumbnail" @@ -142,7 +141,7 @@ pub fun createAddressRaffle(_ acct: Test.Account): UInt64 { return createEvent.raffleID } -pub fun drawFromRaffle(_ signer: Test.Account, _ addr: Address, _ id: UInt64): String { +access(all) fun drawFromRaffle(_ signer: Test.TestAccount, _ addr: Address, _ id: UInt64): String { txExecutor("draw_from_raffle.cdc", [signer], [addr, id], nil) let drawingEvent = Test.eventsOfType(Type()).removeLast() as! FlowtyRaffles.RaffleReceiptRevealed diff --git a/test/test_helpers.cdc b/test/test_helpers.cdc index 2936a80..3df8b64 100644 --- a/test/test_helpers.cdc +++ b/test/test_helpers.cdc @@ -5,11 +5,11 @@ import Test https://github.com/onflow/hybrid-custody/blob/main/test/test_helpers.cdc */ -pub fun loadCode(_ fileName: String, _ baseDirectory: String): String { +access(all) fun loadCode(_ fileName: String, _ baseDirectory: String): String { return Test.readFile("../".concat(baseDirectory).concat("/").concat(fileName)) } -pub fun scriptExecutor(_ scriptName: String, _ arguments: [AnyStruct]): AnyStruct? { +access(all) fun scriptExecutor(_ scriptName: String, _ arguments: [AnyStruct]): AnyStruct? { let scriptCode = loadCode(scriptName, "scripts") let scriptResult = Test.executeScript(scriptCode, arguments) @@ -22,9 +22,9 @@ pub fun scriptExecutor(_ scriptName: String, _ arguments: [AnyStruct]): AnyStruc return scriptResult.returnValue } -pub fun txExecutor( +access(all) fun txExecutor( _ txName: String, - _ signers: [Test.Account], + _ signers: [Test.TestAccount], _ arguments: [AnyStruct], _ expectedError: String? ): Bool { diff --git a/transactions/add_entries_to_raffle.cdc b/transactions/add_entries_to_raffle.cdc index 20270bd..cbeb0d2 100644 --- a/transactions/add_entries_to_raffle.cdc +++ b/transactions/add_entries_to_raffle.cdc @@ -1,8 +1,8 @@ import "FlowtyRaffles" transaction(raffleID: UInt64, entries: [AnyStruct]) { - prepare(acct: AuthAccount) { - let manager = acct.borrow<&FlowtyRaffles.Manager>(from: FlowtyRaffles.ManagerStoragePath) + prepare(acct: auth(Capabilities, Storage) &Account) { + let manager = acct.storage.borrow(from: FlowtyRaffles.ManagerStoragePath) ?? panic("raffles manager not found") let raffle = manager.borrowRaffle(id: raffleID) ?? panic("raffle not found") diff --git a/transactions/add_entry_to_raffle.cdc b/transactions/add_entry_to_raffle.cdc index f928dcd..b25e9d4 100644 --- a/transactions/add_entry_to_raffle.cdc +++ b/transactions/add_entry_to_raffle.cdc @@ -1,8 +1,8 @@ import "FlowtyRaffles" transaction(raffleID: UInt64, entry: AnyStruct) { - prepare(acct: AuthAccount) { - let manager = acct.borrow<&FlowtyRaffles.Manager>(from: FlowtyRaffles.ManagerStoragePath) + prepare(acct: auth(Capabilities, Storage) &Account) { + let manager = acct.storage.borrow(from: FlowtyRaffles.ManagerStoragePath) ?? panic("raffles manager not found") let raffle = manager.borrowRaffle(id: raffleID) ?? panic("raffle not found") diff --git a/transactions/create_raffle.cdc b/transactions/create_raffle.cdc index 93f35d9..6ae6563 100644 --- a/transactions/create_raffle.cdc +++ b/transactions/create_raffle.cdc @@ -3,23 +3,26 @@ import "FlowtyRaffleSource" import "MetadataViews" transaction(type: Type, start: UInt64?, end: UInt64?, name: String, description: String, thumbnail: String, externalURL: String, commitBlocksAhead: UInt64, revealers: [Address]?) { - prepare(acct: AuthAccount) { + prepare(acct: auth(Capabilities, Storage) &Account) { let source <- FlowtyRaffleSource.createRaffleSource(entryType: type, removeAfterReveal: false) - if acct.borrow<&AnyResource>(from: FlowtyRaffles.ManagerStoragePath) == nil { - acct.save(<-FlowtyRaffles.createManager(), to: FlowtyRaffles.ManagerStoragePath) - acct.link<&FlowtyRaffles.Manager{FlowtyRaffles.ManagerPublic}>(FlowtyRaffles.ManagerPublicPath, target: FlowtyRaffles.ManagerStoragePath) + if acct.storage.borrow<&AnyResource>(from: FlowtyRaffles.ManagerStoragePath) == nil { + acct.storage.save(<-FlowtyRaffles.createManager(), to: FlowtyRaffles.ManagerStoragePath) + acct.capabilities.publish( + acct.capabilities.storage.issue<&FlowtyRaffles.Manager>(FlowtyRaffles.ManagerStoragePath), + at: FlowtyRaffles.ManagerPublicPath + ) } - let manager = acct.borrow<&FlowtyRaffles.Manager>(from: FlowtyRaffles.ManagerStoragePath) + let manager = acct.storage.borrow(from: FlowtyRaffles.ManagerStoragePath) ?? panic("raffles manager not found") let display = MetadataViews.Display( name: name, description: description, - thumbnail: MetadataViews.HTTPFile(thumbnail) + thumbnail: MetadataViews.HTTPFile(url: thumbnail) ) - let details = FlowtyRaffles.Details(start, end, display, MetadataViews.ExternalURL(externalURL), commitBlocksAhead: commitBlocksAhead) + let details = FlowtyRaffles.Details(start: start, end: end, display: display, externalURL: MetadataViews.ExternalURL(externalURL), commitBlocksAhead: commitBlocksAhead) let id = manager.createRaffle(source: <-source, details: details, revealers: revealers) // make sure you can borrow the raffle back diff --git a/transactions/create_raffle_source.cdc b/transactions/create_raffle_source.cdc index b1cd8f7..447a46a 100644 --- a/transactions/create_raffle_source.cdc +++ b/transactions/create_raffle_source.cdc @@ -1,11 +1,11 @@ import "FlowtyRaffleSource" transaction(type: Type, path: StoragePath) { - prepare(acct: AuthAccount) { + prepare(acct: auth(Capabilities, Storage) &Account) { let source <- FlowtyRaffleSource.createRaffleSource(entryType: type, removeAfterReveal: false) let t = source.getEntryType() assert(t == type) - acct.save(<-source, to: path) + acct.storage.save(<-source, to: path) } } \ No newline at end of file diff --git a/transactions/draw_from_raffle.cdc b/transactions/draw_from_raffle.cdc index 9b5e449..3d7c32f 100644 --- a/transactions/draw_from_raffle.cdc +++ b/transactions/draw_from_raffle.cdc @@ -1,12 +1,12 @@ import "FlowtyRaffles" transaction(addr: Address, id: UInt64) { - prepare(acct: AuthAccount) { - let manager = acct.borrow<&FlowtyRaffles.Manager>(from: FlowtyRaffles.ManagerStoragePath) + prepare(acct: auth(Capabilities, Storage) &Account) { + let manager = acct.storage.borrow(from: FlowtyRaffles.ManagerStoragePath) ?? panic("raffles manager not found") let receiptID = manager.commitDrawing(raffleID: id) - let ref = manager as &FlowtyRaffles.Manager{FlowtyRaffles.ManagerPublic} + let ref = manager as &FlowtyRaffles.Manager manager.revealDrawing(manager: ref, raffleID: id, receiptID: receiptID) } } \ No newline at end of file diff --git a/transactions/setup_manager.cdc b/transactions/setup_manager.cdc index 75227ac..4221140 100644 --- a/transactions/setup_manager.cdc +++ b/transactions/setup_manager.cdc @@ -1,9 +1,12 @@ import "FlowtyRaffles" transaction { - prepare(acct: AuthAccount) { + prepare(acct: auth(Capabilities, Storage) &Account) { let manager <- FlowtyRaffles.createManager() - acct.save(<-manager, to: FlowtyRaffles.ManagerStoragePath) - acct.link<&FlowtyRaffles.Manager{FlowtyRaffles.ManagerPublic}>(FlowtyRaffles.ManagerPublicPath, target: FlowtyRaffles.ManagerStoragePath) + acct.storage.save(<-manager, to: FlowtyRaffles.ManagerStoragePath) + acct.capabilities.publish( + acct.capabilities.storage.issue<&FlowtyRaffles.Manager>(FlowtyRaffles.ManagerStoragePath), + at: FlowtyRaffles.ManagerPublicPath + ) } } \ No newline at end of file