-
Notifications
You must be signed in to change notification settings - Fork 6
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
Comments
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
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 ( to deploy the simple storage contract as a private pente contract send the following (after replacing the privacy group address in the {
"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 {
"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 {
"jsonrpc": "2.0",
"id": "000000001",
"result": {
"0": "0"
}
}
and you should be able {
"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"
}
]
}
]
}
|
A few points to note:
|
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" |
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.
The text was updated successfully, but these errors were encountered: