This feature will be implemented in the next Rhône upgrade.
Map simplifies smart contract code for accessing and storing data. ALPH.bet utilizes this feature to manage data like player and round information more efficiently. This article will explain the changes made to adapt the smart contract to use Mapping.
Read our articles about the dapp we developed: part 1, part 2, and part 3.
Overview of Changes
Each time someone places a bet, a contract is deployed with details like the bet amount and specifics. Several functions utilize subcontracts to check if someone has already bet in a round or to withdraw the bet once it's completed.
Previous Implementation Without Mapping
The following code demonstrates how these functions were handled without Mapping:
Checking if someone already played:
assert!(!contractExists!(subContractId!(bidderContractId)), ErrorCodes.AlreadyPlayed)
Deploying a smart contract when someone bets:
let selfContractId = selfContractId!()
let (encodedImmFields, encodedMutFields) = PunterChoice.encodeFields!(selfContractId, caller, epoch, side, bidAmount, claimedByAnyoneTimestamp)
let _ = copyCreateSubContract!{caller -> ALPH: 1 alph}(bidderContractId, punterTemplateId, encodedImmFields, encodedMutFields)
Withdrawing:
let betInfoContractId = subContractId!(SubContractTypes.PunterChoice ++ toByteVec!(addressToClaim) ++ epochNumber)
let roundContractId = subContractId!(SubContractTypes.RoundMultipleChoice ++ epochNumber)
assert!(contractExists!(roundContractId), ErrorCodes.RoundNotExists)
assert!(contractExists!(betInfoContractId), ErrorCodes.PunterChoiceNotExists)
if (contractExists!(roundContractId) && contractExists!(betInfoContractId)){
let betInfoCaller = getBetInfoByEpoch(addressToClaim, epochNumber)
let addressPunterChoice = betInfoCaller.getAddress()
let canBeClaimedAt = betInfoCaller.getClaimedByAnyone()
if(addressToClaim != from){
assert!(timestampNow > canBeClaimedAt, ErrorCodes.CannotBeClaimedYet)
}
let amountPunterChoice = betInfoCaller.getAmountBid()
let upBid = betInfoCaller.getBid()
let round = getRoundByEpochByteVec(epochNumber)
round.userClaimRewards(addressToClaim, amountPunterChoice, upBid)
betInfoCaller.destroy(from)
}
Managing subcontracts can become complex with numerous contracts, which is where mapping becomes useful. This feature reduces complexity and provides a good abstraction.
Key-value pairs are fundamental in computing, linking an identifier (key) to a piece of data (value). For example, the key might be CITY, and the value might be BERLIN. Mapping is a set of key-value pairs.
New Implementation with Mapping
For ALPH.bet, the major change was replacing contract management of players with a simple struct:
struct PunterChoice {
predictionContractId: ByteVec,
punterAddress: Address,
epoch: U256,
side: U256,
amountBid: U256,
claimedByAnyoneAt: U256
}
And adding this map at the beginning of the contract:
mapping[ByteVec, PunterChoice] punters
This simplifies checking if someone has already played:
assert!(!punters.contains!(bidderKey), ErrorCodes.AlreadyPlayed)
Previously, accessing player information involved computing the path and checking for subcontract existence:
fn getBetInfoByEpoch(from: Address, epochToGet: ByteVec) -> PunterChoice {
let bidderContractId = subContractId!(SubContractTypes.PunterChoice ++ toByteVec!(from) ++ epochToGet)
assert!(contractExists!(bidderContractId), ErrorCodes.PunterChoiceNotExists)
return PunterChoice(bidderContractId)
}
This has been replaced by one line, with the subcontract ID becoming the map key:
let betInfoContractKey = SubContractTypes.PunterChoice ++ toByteVec!(addressToClaim) ++ epochNumber
let betInfoCaller = punters[betInfoContractKey]
So when the refactor is done, the withdrawing function will look like this. The example below demonstrate how easy it is to access to player information and delete (=get back locked ALPH) the entry.
let roundContractId = subContractId!(SubContractTypes.RoundMultipleChoice ++ epochNumber)
let betInfoContractKey = SubContractTypes.PunterChoice ++ toByteVec!(addressToClaim) ++ epochNumber
assert!(contractExists!(roundContractId), ErrorCodes.RoundNotExists)
assert!(punters.contains!(betInfoContractKey), ErrorCodes.PunterChoiceNotExists)
if (contractExists!(roundContractId) && contractExists!(betInfoContractKey)){
let betInfoCaller = punters[betInfoContractKey]
let addressPunterChoice = betInfoCaller.punterAddress
let canBeClaimedAt = betInfoCaller.claimedByAnyoneAt
if(addressToClaim != from){
assert!(timestampNow > canBeClaimedAt, ErrorCodes.CannotBeClaimedYet)
}
let amountPunterChoice = betInfoCaller.amountBid
let upBid = betInfoCaller.side
let round = getRoundByEpochByteVec(epochNumber)
round.userClaimRewards(addressToClaim, amountPunterChoice, upBid)
punters.remove!(from, betInfoContractKey)
}
Conclusion
This new feature significantly aids in writing cleaner and more optimized code. The abstraction enhances the developer experience by simplifying the logic behind managing and deploying subcontracts.
Although the smart contract isn't much shorter, it is cleaner and greatly reduces the complexity of using subcontracts with Ralph.
This version maintains the technical detail while improving readability and emphasizing the benefits of the changes.