Solidity Mapping – How to Randomly Shuffle a Mapping in Solidity

contract-designcontract-developmentmappingsolidity

I am trying to randomly shuffle a mapping in Solidity. As I understand, this problem has 2 parts:

  1. Generating a random number in a smart contract using a secure manner
  2. Shuffling the mapping – I'm more interested in this part

To give you a clear scenario, I have a mapping of dogs, organized by IDs starting from 0. I have a function called feedDog, that is called using a dogID. After one person feeds the dog by calling the feedDog function, I want to somehow shuffle the IDs of the dog, so nobody knows who fed which dog.

struct Dog{
            string dogName;
            uint256 foodReceived; 
            uint256 dogID;
        }
    
    mapping(uint256 => Dog) private mappingOfDogs;
    
    function giveFood(uint256 dogID) public {
            mappingOfDogs[dogID].foodReceived++;  
            // random shuffling the mappingOfDogs occurs here, most likely
    
      
        }

Output:

  1. Before the first call of giveFood function – Mapping of dogs: | 0.Charlie | 1.Copper | 2.Max |
  2. After the first call of giveFood function – Mapping of dogs: | 0.Copper | 1.Max | 2.Charlie |
  3. After the second call of giveFood function – Mapping of dogs: | 0.Charlie | 1.Max | 2. Copper |

I know that mappings aren't like arrays, but what if we retain the IDs in a separate array? Is there any way of doing this somehow?

Best Answer

With your response assuming that you can use an array there is a really straight forward solution!

You can use the Fisher-Yates shuffle!

The jist of it is that you are gonna iterate through the full array, generate a random number within the range of your array, and swap the positions of the current i of your loop with the position of the random number you generated.

Here is a pseudo-code untested implementation

function shuffleArray(uint256[] memory array) {
  let curId = array.length;
  // There remain elements to shuffle
  while (0 !== curId) {
    // Pick a remaining element
    uint256 randId = randomNumber() % array.length; //Assuming you already have your randomNumber function
    curId -= 1;
    // Swap it with the current element.
    uint256 tmp = array[curId];
    array[curId] = array[randId];
    array[randId] = tmp;
  }
  return array;
}

As a side note, this type of operation takes a lot of gas so it's not ideal to do it on chain.

Also as you stated in your comment, this does not hide who sent the transaction. If you explain what you want to achieve in another question, there are other ways to potentially hide which individual did what which would take less gas.

Related Topic