Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: Basic hello world private EVM sample #448

Open
hosie opened this issue Nov 22, 2024 · 3 comments
Open

docs: Basic hello world private EVM sample #448

hosie opened this issue Nov 22, 2024 · 3 comments
Labels
documentation Improvements or additions to documentation

Comments

@hosie
Copy link
Contributor

hosie commented Nov 22, 2024

Current State

There is currently a bit of a jump in the first use experience. The getting started guide takes user from standing start, with very easy to follow instructions to the point of having a devnet deployed. Then we go to the first tutorial which is a very powerful use case and turn key automation to deploy it and run it. So the cognitive load on the user to get something real running is very low. However, we do skip over some basics that the user would need to get a grasp on if they want to start experimenting with their own use case.

Desired State

I propose that a very basic Pente tutorial that shows how to take something like the typical "simple storage" contract and deploy that as a private evm contract in paladin, invoke it and inspect the distribution of states etc..

One specific step that is not immediately intuitive in this flow is how to take the ABI that is produced by the solc compiler and convert that to the correct format needed to deploy it to a pente privacy group and to invoke it via private transactions.

@hosie hosie added the documentation Improvements or additions to documentation label Nov 22, 2024
@hosie
Copy link
Contributor Author

hosie commented Nov 22, 2024

Some thoughts about the information / snippets that would be useful in such a tutorial

Starting with the following solidity

//Tell the Solidity compiler what version to use
pragma solidity ^0.8.27;

//Declares a new contract
contract SimpleStorage {
    //Storage. Persists in between transactions
    uint x;

    constructor(uint initialValue) public {
        x = initialValue;
    }
    //Allows the unsigned integer stored to be changed
    function set(uint newValue) public {
        x = newValue;
    }
    
    //Returns the currently stored unsigned integer
    function get() public view returns (uint) {
        return x;
    }
}

once compiled, produces bytecode

0x6080604052348015600f57600080fd5b506040516101f83803806101f88339818101604052810190602f91906071565b80600081905550506099565b600080fd5b6000819050919050565b6051816040565b8114605b57600080fd5b50565b600081519050606b81604a565b92915050565b6000602082840312156084576083603b565b5b6000609084828501605e565b91505092915050565b610150806100a86000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806360fe47b11461003b5780636d4ce63c14610057575b600080fd5b610055600480360381019061005091906100c3565b610075565b005b61005f61007f565b60405161006c91906100ff565b60405180910390f35b8060008190555050565b60008054905090565b600080fd5b6000819050919050565b6100a08161008d565b81146100ab57600080fd5b50565b6000813590506100bd81610097565b92915050565b6000602082840312156100d9576100d8610088565b5b60006100e7848285016100ae565b91505092915050565b6100f98161008d565b82525050565b600060208201905061011460008301846100f0565b9291505056fea2646970667358221220cde098d75f3c72804ab011f79b0d9b8491b2721638dacea49b0bb07c85e7a2ef64736f6c634300081b0033

and abi

[
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "initialValue",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "get",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "newValue",
        "type": "uint256"
      }
    ],
    "name": "set",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  }
]

To create a new Pente privacy group, send the following

{
  "jsonrpc": "2.0",
  "id": "000000001",
  "method": "ptx_sendTransaction",
  "params": [
    {
      "type": "private",
      "domain": "pente",
      "from": "alice@node1",
      "data": {
        "endorsementType": "group_scoped_identities",
        "evmVersion": "shanghai",
        "externalCallsEnabled": true,
        "group": {
          "members": [
            "alice@node1",
            "bob@node2",
            "carol@node3"
          ],
          "salt": "0xbd05ffa224edd2b3079b7a9bbe6852156b5a7244fd012b24e66b3db324f5122c"
        }
      },
      "abi": [
        {
          "type": "constructor",
          "inputs": [
            {
              "name": "group",
              "type": "tuple",
              "components": [
                {
                  "name": "salt",
                  "type": "bytes32"
                },
                {
                  "name": "members",
                  "type": "string[]"
                }
              ]
            },
            {
              "name": "evmVersion",
              "type": "string"
            },
            {
              "name": "endorsementType",
              "type": "string"
            },
            {
              "name": "externalCallsEnabled",
              "type": "bool"
            }
          ],
          "outputs": null
        }
      ]
    }
  ]
}

Take a note of the transaction id from the response. e.g.

{
  "jsonrpc": "2.0",
  "id": "000000001",
  "result": "b3eda156-37ec-43d9-8127-fef2b5cb428f"
}

using that transaction id, send the following

{
  "jsonrpc": "2.0",
  "id": "000000001",
  "method": "ptx_getTransactionReceipt",
  "params": [
    "b3eda156-37ec-43d9-8127-fef2b5cb428f"
  ]

until you receive a receipt...

{
  "jsonrpc": "2.0",
  "id": "000000001",
  "result": {
    "id": "b3eda156-37ec-43d9-8127-fef2b5cb428f",
    "domain": "pente",
    "success": true,
    "transactionHash": "0xef8100321de2c70e20fed5920d7e79b5643e1649b07e5ae104d5e2a2d989ff85",
    "blockNumber": 61,
    "logIndex": 1,
    "source": "0xc1cb98af38e6c3f5d7d4a5d382eb001d5ec686ca",
    "contractAddress": "0xcf786674433ae9057a8dd9af1ceea100a0b5b891"
  }
}

Take a note of the privacy group address (contractAddress) from the receipt

to deploy the simple storage contract as a private pente contract send the following (after replacing the privacy group address in the to field)

{
  "jsonrpc": "2.0",
  "id": "000000001",
  "method": "ptx_sendTransaction",
  "params": [
    {
      "type": "private",
      "domain": "pente",
      "function": "deploy",
      "from": "alice",
      "to": "0xcf786674433ae9057a8dd9af1ceea100a0b5b891",
      "data": {
        "bytecode": "0x6080604052348015600f57600080fd5b506040516101f83803806101f88339818101604052810190602f91906071565b80600081905550506099565b600080fd5b6000819050919050565b6051816040565b8114605b57600080fd5b50565b600081519050606b81604a565b92915050565b6000602082840312156084576083603b565b5b6000609084828501605e565b91505092915050565b610150806100a86000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806360fe47b11461003b5780636d4ce63c14610057575b600080fd5b610055600480360381019061005091906100c3565b610075565b005b61005f61007f565b60405161006c91906100ff565b60405180910390f35b8060008190555050565b60008054905090565b600080fd5b6000819050919050565b6100a08161008d565b81146100ab57600080fd5b50565b6000813590506100bd81610097565b92915050565b6000602082840312156100d9576100d8610088565b5b60006100e7848285016100ae565b91505092915050565b6100f98161008d565b82525050565b600060208201905061011460008301846100f0565b9291505056fea2646970667358221220cde098d75f3c72804ab011f79b0d9b8491b2721638dacea49b0bb07c85e7a2ef64736f6c634300081b0033",
        "group": {
          "members": [
            "alice@node1",
            "bob@node2",
            "carol@node3"
          ],
          "salt": "0xbd05ffa224edd2b3079b7a9bbe6852156b5a7244fd012b24e66b3db324f5122c"
        },
        "inputs": {
          "initialValue": 0
        }
      },
      "abi": [
        {
          "type": "function",
          "inputs": [
            {
              "name": "group",
              "type": "tuple",
              "components": [
                {
                  "name": "salt",
                  "type": "bytes32"
                },
                {
                  "name": "members",
                  "type": "string[]"
                }
              ]
            },
            {
              "name": "to",
              "type": "address"
            },
            {
              "name": "inputs",
              "type": "tuple",
              "components": [],
              "outputs": null
            }
          ],
          "name": "get",
          "outputs": [
            {
              "internalType": "uint256",
              "name": "",
              "type": "uint256"
            }
          ]
        },
        {
          "type": "function",
          "inputs": [
            {
              "name": "group",
              "type": "tuple",
              "components": [
                {
                  "name": "salt",
                  "type": "bytes32"
                },
                {
                  "name": "members",
                  "type": "string[]"
                }
              ]
            },
            {
              "name": "to",
              "type": "address"
            },
            {
              "name": "inputs",
              "type": "tuple",
              "components": [
                {
                  "internalType": "uint256",
                  "name": "newValue",
                  "type": "uint256"
                }
              ],
              "outputs": null
            }
          ],
          "name": "set",
          "outputs": []
        },
        {
          "type": "function",
          "inputs": [
            {
              "name": "group",
              "type": "tuple",
              "components": [
                {
                  "name": "salt",
                  "type": "bytes32"
                },
                {
                  "name": "members",
                  "type": "string[]"
                }
              ]
            },
            {
              "name": "bytecode",
              "type": "bytes"
            },
            {
              "name": "inputs",
              "type": "tuple",
              "components": [
                {
                  "internalType": "uint256",
                  "name": "initialValue",
                  "type": "uint256"
                }
              ],
              "outputs": null
            }
          ],
          "name": "deploy"
        }
      ]
    }
  ]
}

again, take a note of the transaction id and this time poll for the domain receipt

{
  "jsonrpc": "2.0",
  "id": "000000001",
  "method": "ptx_getDomainReceipt",
  "params": [
    "pente",
    "3e3cc7f2-ffab-4a2d-95b0-c46bbbb2d14d"
  ]
}

until you see something like

{
  "jsonrpc": "2.0",
  "id": "000000001",
  "result": {
    "transaction": {
      "from": "0x543ac4bc911ac75acda49c39499d229cbb0c1079",
      "to": null,
      "nonce": "0x0",
      "gas": "0x0",
      "value": "0x0",
      "data": "0x6080604052348015600f57600080fd5b506040516101f83803806101f88339818101604052810190602f91906071565b80600081905550506099565b600080fd5b6000819050919050565b6051816040565b8114605b57600080fd5b50565b600081519050606b81604a565b92915050565b6000602082840312156084576083603b565b5b6000609084828501605e565b91505092915050565b610150806100a86000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806360fe47b11461003b5780636d4ce63c14610057575b600080fd5b610055600480360381019061005091906100c3565b610075565b005b61005f61007f565b60405161006c91906100ff565b60405180910390f35b8060008190555050565b60008054905090565b600080fd5b6000819050919050565b6100a08161008d565b81146100ab57600080fd5b50565b6000813590506100bd81610097565b92915050565b6000602082840312156100d9576100d8610088565b5b60006100e7848285016100ae565b91505092915050565b6100f98161008d565b82525050565b600060208201905061011460008301846100f0565b9291505056fea2646970667358221220cde098d75f3c72804ab011f79b0d9b8491b2721638dacea49b0bb07c85e7a2ef64736f6c634300081b00330000000000000000000000000000000000000000000000000000000000000000"
    },
    "receipt": {
      "from": "0x543ac4bc911ac75acda49c39499d229cbb0c1079",
      "to": null,
      "gasUsed": "0x110ce",
      "contractAddress": "0x88f507a79ab80a81fc5020ce1470d347931f2300",
      "logs": []
    }
  }
}

take a note of contractAddress from the domain receipt. This is the address of the SimpleStorage contract within your privacy groups private EVM. You can then invoke inspect the state of the contract via the ptc_call function

{
  "jsonrpc": "2.0",
  "id": "000000001",
  "method": "ptx_call",
  "params": [
    {
      "type": "private",
      "domain": "pente",
      "function": "get",
      "from": "alice",
      "to": "0xcf786674433ae9057a8dd9af1ceea100a0b5b891",
      "data": {
        "to": "0x88f507a79ab80a81fc5020ce1470d347931f2300",
        "group": {
          "members": [
            "alice@node1",
            "bob@node2",
            "carol@node3"
          ],
          "salt": "0xbd05ffa224edd2b3079b7a9bbe6852156b5a7244fd012b24e66b3db324f5122c"
        },
        "inputs": {}
      },
      "abi": [
        {
          "type": "function",
          "inputs": [
            {
              "name": "group",
              "type": "tuple",
              "components": [
                {
                  "name": "salt",
                  "type": "bytes32"
                },
                {
                  "name": "members",
                  "type": "string[]"
                }
              ]
            },
            {
              "name": "to",
              "type": "address"
            },
            {
              "name": "inputs",
              "type": "tuple",
              "components": [],
              "outputs": null
            }
          ],
          "name": "get",
          "outputs": [
            {
              "internalType": "uint256",
              "name": "",
              "type": "uint256"
            }
          ]
        },
        {
          "type": "function",
          "inputs": [
            {
              "name": "group",
              "type": "tuple",
              "components": [
                {
                  "name": "salt",
                  "type": "bytes32"
                },
                {
                  "name": "members",
                  "type": "string[]"
                }
              ]
            },
            {
              "name": "to",
              "type": "address"
            },
            {
              "name": "inputs",
              "type": "tuple",
              "components": [
                {
                  "internalType": "uint256",
                  "name": "newValue",
                  "type": "uint256"
                }
              ],
              "outputs": null
            }
          ],
          "name": "set",
          "outputs": []
        },
        {
          "type": "function",
          "inputs": [
            {
              "name": "group",
              "type": "tuple",
              "components": [
                {
                  "name": "salt",
                  "type": "bytes32"
                },
                {
                  "name": "members",
                  "type": "string[]"
                }
              ]
            },
            {
              "name": "bytecode",
              "type": "bytes"
            },
            {
              "name": "inputs",
              "type": "tuple",
              "components": [
                {
                  "internalType": "uint256",
                  "name": "initialValue",
                  "type": "uint256"
                }
              ],
              "outputs": null
            }
          ],
          "name": "deploy"
        }
      ]
    }
  ]
}

which should return the value 0 because that is what we specified as the initial value in the deploy function above

{
  "jsonrpc": "2.0",
  "id": "000000001",
  "result": {
    "0": "0"
  }
}

and you should be able set the value by sending the following

{
  "jsonrpc": "2.0",
  "id": "000000001",
  "method": "ptx_sendTransaction",
  "params": [
    {
      "type": "private",
      "domain": "pente",
      "function": "set",
      "from": "alice",
      "to": "0xcf786674433ae9057a8dd9af1ceea100a0b5b891",
      "data": {
        "to": "0x88f507a79ab80a81fc5020ce1470d347931f2300",
        "group": {
          "members": [
            "alice@node1",
            "bob@node2",
            "carol@node3"
          ],
          "salt": "0xbd05ffa224edd2b3079b7a9bbe6852156b5a7244fd012b24e66b3db324f5122c"
        },
        "inputs": {
          "newValue": 42
        }
      },
      "abi": [
        {
          "type": "function",
          "inputs": [
            {
              "name": "group",
              "type": "tuple",
              "components": [
                {
                  "name": "salt",
                  "type": "bytes32"
                },
                {
                  "name": "members",
                  "type": "string[]"
                }
              ]
            },
            {
              "name": "to",
              "type": "address"
            },
            {
              "name": "inputs",
              "type": "tuple",
              "components": [],
              "outputs": null
            }
          ],
          "name": "get",
          "outputs": [
            {
              "internalType": "uint256",
              "name": "",
              "type": "uint256"
            }
          ]
        },
        {
          "type": "function",
          "inputs": [
            {
              "name": "group",
              "type": "tuple",
              "components": [
                {
                  "name": "salt",
                  "type": "bytes32"
                },
                {
                  "name": "members",
                  "type": "string[]"
                }
              ]
            },
            {
              "name": "to",
              "type": "address"
            },
            {
              "name": "inputs",
              "type": "tuple",
              "components": [
                {
                  "internalType": "uint256",
                  "name": "newValue",
                  "type": "uint256"
                }
              ],
              "outputs": null
            }
          ],
          "name": "set",
          "outputs": []
        },
        {
          "type": "function",
          "inputs": [
            {
              "name": "group",
              "type": "tuple",
              "components": [
                {
                  "name": "salt",
                  "type": "bytes32"
                },
                {
                  "name": "members",
                  "type": "string[]"
                }
              ]
            },
            {
              "name": "bytecode",
              "type": "bytes"
            },
            {
              "name": "inputs",
              "type": "tuple",
              "components": [
                {
                  "internalType": "uint256",
                  "name": "initialValue",
                  "type": "uint256"
                }
              ],
              "outputs": null
            }
          ],
          "name": "deploy"
        }
      ]
    }
  ]
}

@hosie
Copy link
Contributor Author

hosie commented Nov 22, 2024

A few points to note:

  • every time you create a new privacy group, you must specific a new unique salt and then you must use that value for any subsequent call to that privacy group.
  • the ABI in the ptx_sendTransaction payloads is not identical to but is derived from the ABI produced by the solidity compiler.
  • the ABI for each function is wrapped with and ABI structure containing group and to fields
  • the ABI for the constructor is converted to a function named deploy then wrapped with an ABI structure containing group and bytecode fields ( but no to)

@hosie
Copy link
Contributor Author

hosie commented Nov 22, 2024

The following bash script can be used to convert the ABI produced by solc

#!/bin/bash
contractABI=$1

# all functions, including constructors take group as the first argument
boilerPlateFunctionCommon=$(cat <<EOF
  {
    "type": "function",
    "inputs": [
      {
        "name": "group",
        "type": "tuple",
        "components": [
          {
            "name": "salt",
            "type": "bytes32"
          },
          {
            "name": "members",
            "type": "string[]"
          }
        ]
      }
    ]
  }
EOF
)

# non constuctor functions have a to field
boilerPlateFunction=$(
  echo "$boilerPlateFunctionCommon" | jq '. | .inputs+=  [{ "name": "to", "type": "address" }] ' 
)

# constructor functions have a bytecode field and the function name is always deploy
boilerPlateConstructor=$(
  echo "$boilerPlateFunctionCommon" | jq '. | .inputs+=      [{ "name": "bytecode", "type": "bytes" }] | .name = "deploy"' 
)


contractConstructorABI=$(
  echo "$contractABI" | \
  jq '.[] | select(.type == "constructor")'
)

wrappedABI=$(
  echo "$contractABI" | \
  jq \
     --argjson boilerPlateFunction "$boilerPlateFunction" \
     '[ .[] | select(.type == "function") as $contractFunction | ( $boilerPlateFunction | .inputs += [{name:"inputs",type:"tuple",components: $contractFunction.inputs,outputs:null}] | .name = $contractFunction.name | .outputs = $contractFunction.outputs ) ]' 
)

wrappedABI=$(
  echo $wrappedABI | \
    jq --argjson contractConstructorABI "$contractConstructorABI" \
     --argjson boilerPlateConstructor "$boilerPlateConstructor" \
     '. += [( $boilerPlateConstructor |  .inputs += [{name:"inputs",type:"tuple",components: $contractConstructorABI.inputs,outputs:null}]  )]'
)

echo "$wrappedABI" 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests

1 participant