So we've managed to get the majority of our website working.
However, there is one key downfall and you might have already realised this.
The problem is when we upgrade our canister here, then it's going to reset the state of our token canister
in our main document.
Hoku file.
Let me show you what I mean.
If I set the local variable for my token canister into the canister public key variable, and then I
transfer half a billion tokens into that canister.
Then in here, if I pass in my canisters, I'd be using the canister it token and paste that in here.
You can see my canister has half a billion down.
Now, if I claim some dang tokens and check my dying balance, then it has gone down.
Now if I actually change the code in my token canister, say just randomly add in a debug print.
Something that doesn't really do anything, like just says hello.
It's important that it can't be a commons, so we actually have to change the code.
So a print statement will do it.
It will force our canister to upgrade.
We're going to run, do deploy again to redeploy and upgrade our canisters.
And you can see our hello being printed out, which means that is now working.
But if I go back to my frontend.
And I check the balance of my canister.
It has now reset back to zero down.
Now this is because our balances hash map is not stable across the upgrades.
So when our code gets deployed, actually several things happen behind the scenes.
The canister gets created if it hasn't already been.
Then it gets installed and it gets upgraded if there are changes in the code.
Now, when there are changes in the code and this upgrade happens, these variables which are not stable,
will get reset to their initial values because we're putting in the owner as well as the total supply
in here.
Then it's just going to be reset to these values, which, by the way, I've just noticed could actually
be constants instead, because we've never really changed them in our code.
Now, if we want this to be stable, you've seen that we can simply just add the stable keyword in front.
However, as soon as I do that, you'll notice that we get a red underline and it's telling us that
the hash map is a non stable type.
So what do we do?
Well, we can't use that keyword then with our balances variable.
But we really want to keep its features of having this key value pair to note who is the owner and how
much token they own.
What can we do to get around this?
Well, a common pattern in Mutoko is to do this through a temporary variable, and that temporary variable
will have a stable type.
And then before the code gets upgraded, we transfer the data into the temporary variable and then after
the upgrade gets happened, we transfer the data back.
Now, why do we want to go through that process?
Well, it's because in this case, the stable type that we'll need to use is an array.
I'm going to create that array here, which will be a stable variable, which I'll call balance entries.
And this is going to be an array of tuples and a tuple, if you might remember from previous lessons,
is something that can contain different types.
The first type that it's going to contain is the principle type, and the second is going to be a natural
number matching up with our hash map key value pair data types.
Now we're going to start this array off as an empty array.
But going back to this question of why can't we simply just not use a hash map?
Why can't we just simply use this array?
So then balances is a stable array that contains principal and natural number types, and these tuples
then go into this array and can be saved in between upgrades.
Well, the reason is because this is a serialized data type and it's really expensive in terms of time
and in terms of the Internet computer that will be money and computation as well to try and get hold
of items in it because it's ordered in an array.
It's much easier if you knew which item of the array you wanted, but if you actually wanted to search
through it and find each principal and then check it against the one that we want, which is what we're
doing, very often we're doing balances dot get or when we're doing balances dot put.
This would be really, really inefficient using an array data type.
That's why we're only going to change it into an array in between the update and then change it back
once we're done.
So how do we actually do this?
Well, there's actually a example in the documentation where they also show you that here they have
a hash map.
And that map, unfortunately, is not stable.
And so we have to tap into the pre and post upgrade methods in order to shift it into an array similar
to what they've got here before the upgrade.
And then once the upgrade happens, we will then shift it back.
Let's go ahead and tap into these methods.
Now these methods are system methods, so you'll see that they have the keyword system in front of the
func keyword.
So this system func.
Pre upgrade.
And make sure you spell these right because they are system methods.
So if in doubt, just simply copy and paste and let's add a semicolon there.
So this will not error out.
And once we've created the pre upgrade method, I'm just going to copy and paste and update it to turn
it into the post upgrade method.
Now we've got our two methods and we know that this is going to be triggered before the upgrade.
And this is going to be triggered shortly after the upgrade.
Then this is where we're going to write all code in order to transfer our balances into our balance
entries and vice versa.
In our pre upgrade, we're going to set the balance entries array and remember we use that colon equal
sign in order to reassign it a new value and we're going to assign it to what's inside the balances
hash map.
Now, in order to turn this hash map into an array, we're going to need to borrow a method from another
type, which is the editor or the iterator.
And this type has a method called from array and to array, which allows us to iterate through our hash
map and collect each of the elements into an array.
And this is sort of what it looks like.
So let's go ahead and import it.
And use it in a pre upgrade.
So we're going to say it a dot to array and we're going to turn our balance into the array.
However, the balance itself is not it writable.
So in order to make it iterable, if you look in the docs for hash map, the entries is a method which
is going to take what's inside the hash map and return something that can be iterated over so irritable.
Lets happen to our balances hash map.
Make sure that you spelt it right again.
I'm just going to copy and paste and we're going to get the entries.
Entries and then because it's a method we're going to call it with our parentheses.
And so this is going to take our balances hash map and this method will iterate through each of the
items and give us something that we can use the iterator to turn it into an array, which we then save
inside balance entries once the pre upgrade is done.
So we've shifted our entries from the hash map into the array.
Then the upgrade is going to happen, which is going to clear this and reset it to an empty hash map.
But because our balance entries is stable, then this data is going to be kept stable through that update.
And once that's done, we're going to go into the post upgrade where we shift it back.
To do that, we tap into our balances hash map and we again use the colon equal sign to assign it a
new value.
And the new value is going to be a hash map from editor.
And then we need to define the data types of the key and the value pairs.
The key is going to be, again, the principle type and the values are going to be natural numbers.
And then we can call this method from it to the first thing we need to give it is our array.
That's called balance entries, but we can't just give it the actual array.
We have to give it something that is iterable.
So we have to tap in to the balance entries dot value and dot value on the array.
Documentation, as you'll find, is a way that we can get back.
And it's a data type of all of the items which will be tuples inside our balance entries.
And then all we have to do is to supply the same arguments that we did when we created our hash map,
which is the starting size, the quality checker, and also the hashing method.
Now we've pretty much completed our pre upgrade and post upgrade dance to be able to move our hash map
into our stable balance entries array and then to get the values back and put it back into the balances
after the upgrade.
Now there's just one more thing we need to fix, which is this balances dot put.
Now we put this in here in order to assign that the owner the total supply of tokens on the first time
when we create our balances ledger.
As you can imagine, every time the upgrade method runs, this code is going to be called and it's going
to reset the ledger to give the owner always the total supply, which is not a very good ledger because
it wouldn't be keeping track of what's happening.
So we can actually cut this from where we've got it inside our main token canister and place it inside
our post upgrade.
And in addition, we're going to add an if statement to check and see if the balances, which is our
hash map, has a size of less than one, because in that case it means that we've basically got a empty
balance.
And if that is the case, then we're going to actually put the owner and the total supply as the only
keys and values inside our balances hash map.
Once this is done, this means that the only way that our balances is modified is going to be through
the transfer method which takes care of the payout and also any sort of balance transfers.
That's really the only method that should have access to this balances and be able to do balances dot
put so we can turn our balances hash map into a private variable.
And this means that will only be able to modify this hash map from within the token actor.
No other file or canister or class will be able to accidentally modify it.
This is probably a good time to make.
Balance entries private as well, because also no other class has any need to modify it.
The only place where it should be modified or changed is in our pre and post upgrade.
So now with pre upgrade and post upgrade in place, we can try that experiment once again.
If I set my canister public key to the canister ID token to save it as a variable and then transfer
into my canister half a billion tokens.
And then let's just check this canister ID once more.
If I copy that ID and check its balance, then you can see it's got half a billion.
Dang.
Now if I take some money out of it from the faucet and check its balance again, it will now be down
10,000.
Dang.
So now let's try the same thing again where we randomly add a debug print.
In here.
Hit, save and run.
Deploy once more in order for the changes to go through.
Now, if we go back into our local host, even if I refresh the website, paste in the canister ID that
I had from previously.
Alternatively, simply running that canister ID token, if you've lost your canister ID and simply copying
that putting in here, if you check your balance, you can see it's continued to keep track of the state
and you can see this time nothing has been lost through the upgrade.
We've managed to turn that ledger into a stable ledger across time and space, and now it's kept track
of our balances.
Now there's just one final thing that we need to fix with our code, which is it's great when we are
using pre and post upgrade in order to set the initial balance.
But if you downloaded this project as it is and you try to run it, it won't go through an upgrade.
It will go through the initial phase, which is create canister, deploy canister.
Now we want there to be an initial balance in the owner.
Even if you haven't gone through upgrade so we can actually copy this if statement and put it right
here below our balances just to cover that eventuality.
In case you're running this project and have never upgraded it, it will still give the owner something
to work with.