So we've managed to get the majority of our website working. 1
However, there is one key downfall and you might have already realised this. 2
The problem is when we upgrade our canister here, then it's going to reset the state of our token canister 3
in our main. 4
mo file. 5
Let me show you what I mean. 6
If I set the local variable for my token canister into the canister public key variable, and then I 7
transfer half a billion tokens into that canister, 8
then in here, if I pass in my canister's id using dfx canister id token and paste that in here, 9
you can see my canister has half a billion DANG. 10
Now, if I claim some DANG tokens and check my DANG balance, then it has gone down. 11
Now if I actually change the code in my token canister, say just randomly add in a debug print, 12
something that doesn't really do anything, like just says hello. 13
It's important that it can't be a comment, so we actually have to change the code. 14
So a print statement will do it. 15
It will force our canister to upgrade. 16
We're going to run dfx deploy again to redeploy and upgrade our canisters. 17
And you can see our hello being printed out, which means that is now working. 18
But if I go back to my frontend 19
and I check the balance of my canister, 20
it has now reset back to zero DANG. 21
Now this is because our balance's HashMap is not stable across the upgrades. 22
So when our code gets deployed, actually several things happen behind the scenes. 23
The canister gets created, if it hasn't already been, 24
then it gets installed and it gets upgraded if there are changes in the code. 25
Now, when there are changes in the code and this upgrade happens, these variables which are not stable, 26
will get reset to their initial values, and because we're putting in the owner as well as the total supply 27
in here 28
then it's just going to be reset to these values, which, by the way, I've just noticed could actually 29
be constants instead, because we've never really changed them in our code. 30
Now, if we want this to be stable, you've seen that we can simply just add the stable keyword in front. 31
However, as soon as I do that, you'll notice that we get a red underline and it's telling us that 32
the HashMap is a non-stable type. 33
So what do we do? 34
Well, we can't use that keyword then with our balances variable. 35
But we really want to keep its features of having this key-value pair to note who is the owner and how 36
much token they own. 37
What can we do to get around this? 38
Well, a common pattern in Motoko is to do this through a temporary variable. And that temporary variable 39
will have a stable type 40
and then before the code gets upgraded, we transfer the data into the temporary variable and then after 41
the upgrade gets happened, we transfer the data back. 42
Now, why do we want to go through that process? 43
Well, it's because in this case, the stable type that we'll need to use is an array. 44
I'm going to create that array here, which will be a stable variable, which I'll call balanceEntries. 45
And this is going to be an array of tuples and a tuple, if you might remember from previous lessons, 46
is something that can contain different types. 47
The first type that it's going to contain is the Principal type, and the second is going to be a natural 48
number matching up with our HashMap key-value pair data types. 49
Now we're going to start this array off as an empty array. 50
But going back to this question of why can't we simply just not use a HashMap? 51
Why can't we just simply use this array? 52
So then balances is a stable array that contains principal and natural number types, and these tuples 53
then go into this array and can be saved in between upgrades. 54
Well, the reason is because this is a serialized data type and it's really expensive in terms of time 55
and in terms of the Internet computer that will be money and computation as well to try and get hold 56
of items in it. Because it's ordered in an array, 57
it's much easier if you knew which item of the array you wanted. But if you actually wanted to search 58
through it and find each principal and then check it against the one that we want, which is what we're 59
doing, very often we're doing balances.get or when we're doing balances.put. 60
This would be really, really inefficient using an array data type. 61
That's why we're only going to change it into an array in between the update and then change it back 62
once we're done. 63
So how do we actually do this? 64
Well, there's actually a example in the documentation where they also show you that here they have 65
a HashMap 66
and that map, unfortunately, is not stable. 67
And so we have to tap into the pre and post upgrade methods in order to shift it into an array similar 68
to what they've got here before the upgrade. 69
And then once the upgrade happens, we will then shift it back. 70
Let's go ahead and tap into these methods. 71
Now these methods are system methods, so you'll see that they have the keyword system in front of the 72
func keyword. 73
So there's system func 74
preupgrade... 75
And make sure you spell these right because they are system methods. 76
So if in doubt, just simply copy and paste. And let's add a semicolon there 77
so this will not error out. 78
And once we've created the preupgrade method, I'm just going to copy and paste and update it to turn 79
it into the postupgrade method. 80
Now we've got our two methods and we know that this is going to be triggered before the upgrade 81
and this is going to be triggered shortly after the upgrade, 82
then this is where we're going to write our code in order to transfer our balances into our balance 83
entries and vice versa. 84
In our preupgrade, we're going to set the balanceEntries array and remember we use that colon equal 85
sign in order to reassign it a new value, and we're going to assign it to what's inside the balances 86
HashMap. 87
Now, in order to turn this HashMap into an array, we're going to need to borrow a method from another 88
type, which is the iter or the iterator. 89
And this type has a method called fromArray and toArray, which allows us to iterate through our Hash 90
Hap and collect each of the elements into an array. 91
And this is sort of what it looks like. 92
So let's go ahead and import it. 93
And use it in our preupgrade. 94
So we're going to say iter.toArray and we're going to turn our balance into the array. 95
However, the balance itself is not iteratable. 96
So in order to make it iteratable, if you look in the docs for HashMap, the entries is a method which 97
is going to take what's inside the HashMap and return something that can be iterated over, so iteratable. 98
Lets tap into our balances HashMap. 99
Make sure that you spelt it right, again 100
I'm just going to copy and paste. And we're going to get the entries... 101
entries... and then because it's a method, we're going to call it with our parentheses. 102
And so this is going to take our balances HashMap and this method will iterate through each of the 103
items and give us something that we can use the iterator to turn it into an array, which we then save 104
inside balanceEntries. Once the preupgrade is done, 105
so we've shifted our entries from the HashMap into the array, 106
then the upgrade is going to happen which is going to clear this and reset it to an empty HashMap. 107
But because our balance entries is stable, then this data is going to be kept stable through that update. 108
And once that's done, we're going to go into the postupgrade where we shift it back. 109
To do that, we tap into our balances HashMap and we again use the colon equal sign to assign it a 110
new value. 111
And the new value is going to be a HashMap from Iter. 112
And then we need to define the data types of the key and the value pairs. 113
The key is going to be, again, the Principal type and the values are going to be natural numbers. 114
And then we can call this method from Iter. The first thing we need to give it is our array 115
that's called balanceEntries, but we can't just give it the actual array. 116
We have to give it something that is iterable. 117
So we have to tap into the balanceEntries.val and .val on the array 118
documentation as you'll find, is a way that we can get back 119
an Iter data type of all of the items which will be tuples inside our balance entries. 120
And then all we have to do is to supply the same arguments that we did when we created our HashMap, 121
which is the starting size, the quality checker, and also the hashing method. 122
Now we've pretty much completed our preupgrade and postupgrade dance to be able to move our HashMap 123
into our stable balance entries array and then to get the values back and put it back into the balances 124
after the upgrade. 125
Now there's just one more thing we need to fix, which is this balances.put. 126
Now we put this in here in order to assign the owner the total supply of tokens on the first time 127
when we create our balances ledger. 128
As you can imagine, every time the upgrade method runs, this code is going to be called and it's going 129
to reset the ledger to give the owner always the total supply, which is not a very good ledger because 130
it wouldn't be keeping track of what's happening. 131
So we can actually cut this from where we've got it inside our main token canister and place it inside 132
our postupgrade. 133
And in addition, we're going to add an if statement to check and see if the balances, which is our 134
HashMap, has a size of less than one, because in that case it means that we've basically got a empty 135
balance. 136
And if that is the case, then we're going to actually put the owner and the total supply as the only 137
keys and values inside our balances HashMap. 138
Once this is done, this means that the only way that our balances is modified is going to be through 139
the transfer method which takes care of the payOut and also any sort of balance transfers. 140
That's really the only method that should have access to this balances and be able to do balances. 141
put. So we can turn our balances HashMap into a private variable. 142
And this means that we'll only be able to modify this HashMap from within the token actor. 143
No other file or canister or class will be able to accidentally modify it. 144
This is probably a good time to make our 145
balanceEntries private as well, because also no other class has any need to modify it. 146
The only place where it should be modified or changed is in our pre and postupgrade. 147
So now with our preupgrade and postupgrade in place, we can try that experiment once again. 148
If I set my canister public key to dfx canister id token to save it as a variable, and then transfer 149
into my canister half a billion tokens. 150
And then let's just check this canister ID once more. 151
If I copy that ID and check its balance, then you can see it's got half a billion 152
DANG. 153
Now if I take some money out of it from the faucet and check its balance again, it will now be down 154
10,000 155
DANG. 156
So now let's try the same thing again where we randomly add a debug print 157
in here. 158
Hit, save and run. 159
Deploy once more in order for the changes to go through. 160
Now, if we go back into our localhost, even if I refresh the website, paste in the canister ID that 161
I had from previously. 162
Alternatively, simply running that dfx canister id token if you've lost your canister id and simply copying 163
that putting it in here, if you check your balance, you can see it's continued to keep track of the state 164
and you can see this time nothing has been lost through the upgrade. 165
We've managed to turn that ledger into a stable ledger across time and space, and now it's kept track 166
of our balances. 167
Now there's just one final thing that we need to fix with our code, which is it's great when we are 168
using pre and postupgrade in order to set the initial balance. 169
But if you downloaded this project as it is and you try to run it, it won't go through an upgrade. 170
It will go through the initial phase, which is create canister deploy canister. 171
Now we want there to be an initial balance and the owner 172
even if you haven't gone through upgrade. So we can actually copy this if statement and put it right 173
here below our balances just to cover that eventuality. 174
In case you're running this project and have never upgraded it, it will still give the owner some DANG 175
to work with.