From 1448e6866c27f8ac57dbf5a4f1be6e4e5ddf2951 Mon Sep 17 00:00:00 2001 From: "calvo.generico" Date: Mon, 9 Dec 2024 16:10:59 -0300 Subject: [PATCH] Added documentation for each contract. --- README.md | 71 +++++++------------------------- bash_scripts/deploy_tokens.sh | 47 ++++++++++++++------- script/Counter.s.sol | 19 --------- src/Counter.sol | 24 ----------- src/PublicScopedBalanceErc20.sol | 22 ++++++++++ src/PublicSelectionErc721.sol | 43 +++++++++++++++---- src/ShareBalanceErc20.sol | 28 +++++++++++++ src/ShareScopedBalanceErc20.sol | 45 ++++++++++++++++---- 8 files changed, 168 insertions(+), 131 deletions(-) delete mode 100644 script/Counter.s.sol delete mode 100644 src/Counter.sol diff --git a/README.md b/README.md index 9265b45..2a05b0a 100644 --- a/README.md +++ b/README.md @@ -1,66 +1,23 @@ -## Foundry +# Double Zero test contracts -**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** +This repo contains a series of contracts meant to be use to test and showcase the [double zero framework](https://github.com/Moonsong-Labs/double-zero). -Foundry consists of: +In order to deploy the contracts in this repo you are going to need [foundry zksync](https://foundry-book.zksync.io/getting-started/installation). -- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). -- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. -- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. -- **Chisel**: Fast, utilitarian, and verbose solidity REPL. +## Contracts -## Documentation +The contracts in this repo are variations of well known standards taking adventage of the privacy and access control +provided by double zero. Each contracts has comments in the source code explaining how they work and why they work. -https://book.getfoundry.sh/ +## Deploy -## Usage +You can deploy all the contracts by running: -### Build - -```shell -$ forge build -``` - -### Test - -```shell -$ forge test +``` bash +export RPC_URL= # i.e.: http://localhost:3050 +export VERIFIER_URL= # i.e.: http://localhost:3070/contract_verification + export PRIV_KEY= # space at the beggining to avoid save pk in shell history. +./bash_scripts/deploy_tokens.sh ``` -### Format - -```shell -$ forge fmt -``` - -### Gas Snapshots - -```shell -$ forge snapshot -``` - -### Anvil - -```shell -$ anvil -``` - -### Deploy - -```shell -$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key -``` - -### Cast - -```shell -$ cast -``` - -### Help - -```shell -$ forge --help -$ anvil --help -$ cast --help -``` +Once the contracts are deployed you can use the double zero explorer to check their status and interact with them. \ No newline at end of file diff --git a/bash_scripts/deploy_tokens.sh b/bash_scripts/deploy_tokens.sh index e8c769e..bc0fb63 100755 --- a/bash_scripts/deploy_tokens.sh +++ b/bash_scripts/deploy_tokens.sh @@ -3,41 +3,56 @@ set -e set -o xtrace +if [[ -z "${PRIV_KEY}" ]]; then + echo "missing PRIV_KEY env var" + exit 1 +fi + +if [[ -z "${RPC_URL}" ]]; then + echo "missing RPC_URL env var" + exit 1 +fi + +if [[ -z "${VERIFIER_URL}" ]]; then + echo "missing VERIFIER_URL env var" + exit 1 +fi + forge create --zksync \ - --rpc-url http://localhost:3050 \ - --private-key 0x87ce66d9f787696d21af61750c1c3310099f27fb7e7259a193a8c514293a7c0c \ + --rpc-url $RPC_URL \ + --private-key $PRIV_KEY \ --verify \ --verifier zksync \ - --verifier-url http://localhost:3070/contract_verification \ + --verifier-url VERIFIER_URL \ src/PublicScopedBalanceErc20.sol:PublicScopedBalanceErc20 \ - --constructor-args "PublicScopedBalanceErc20-posta" "PEB20P"; + --constructor-args "PublicScopedBalanceErc20" "PEB20"; forge create --zksync \ - --rpc-url http://localhost:3050 \ - --private-key 0x87ce66d9f787696d21af61750c1c3310099f27fb7e7259a193a8c514293a7c0c \ + --rpc-url $RPC_URL \ + --private-key $PRIV_KEY \ --verify \ --verifier zksync \ - --verifier-url http://localhost:3070/contract_verification \ + --verifier-url VERIFIER_URL \ src/ShareBalanceErc20.sol:ShareBalanceErc20 \ - --constructor-args "ShareBalanceErc20-posta" "SB20P"; + --constructor-args "ShareBalanceErc20" "SB20"; forge create --zksync \ - --rpc-url http://localhost:3050 \ - --private-key 0x87ce66d9f787696d21af61750c1c3310099f27fb7e7259a193a8c514293a7c0c \ + --rpc-url $RPC_URL \ + --private-key $PRIV_KEY \ --verify \ --verifier zksync \ - --verifier-url http://localhost:3070/contract_verification \ + --verifier-url VERIFIER_URL \ src/ShareScopedBalanceErc20.sol:ShareScopedBalanceErc20 \ - --constructor-args "ShareScopedBalanceErc20-posta" "SSBE20P"; + --constructor-args "ShareScopedBalanceErc20" "SSBE20"; forge create --zksync \ - --rpc-url http://localhost:3050 \ - --private-key 0x87ce66d9f787696d21af61750c1c3310099f27fb7e7259a193a8c514293a7c0c \ + --rpc-url $RPC_URL \ + --private-key $PRIV_KEY \ --verify \ --verifier zksync \ - --verifier-url http://localhost:3070/contract_verification \ + --verifier-url VERIFIER_URL \ src/PublicSelectionErc721.sol:PublicSelectionErc721 \ - --constructor-args "PublicSelectionErc721-posta" "PC721P"; + --constructor-args "PublicSelectionErc721" "PC721"; diff --git a/script/Counter.s.sol b/script/Counter.s.sol deleted file mode 100644 index cdc1fe9..0000000 --- a/script/Counter.s.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Script, console} from "forge-std/Script.sol"; -import {Counter} from "../src/Counter.sol"; - -contract CounterScript is Script { - Counter public counter; - - function setUp() public {} - - function run() public { - vm.startBroadcast(); - - counter = new Counter(); - - vm.stopBroadcast(); - } -} diff --git a/src/Counter.sol b/src/Counter.sol deleted file mode 100644 index 582041c..0000000 --- a/src/Counter.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; - -contract Counter is Ownable { - uint256 private number; - - constructor() Ownable(msg.sender) {} - - function getNumber() public view returns (uint256) { - return number; - } - - function setNumber(uint256 newNumber) public onlyOwner { - number = newNumber; - } - - function increment() public onlyOwner { - number++; - } - - receive() external payable {} -} diff --git a/src/PublicScopedBalanceErc20.sol b/src/PublicScopedBalanceErc20.sol index 50f734e..a77df43 100644 --- a/src/PublicScopedBalanceErc20.sol +++ b/src/PublicScopedBalanceErc20.sol @@ -4,6 +4,14 @@ pragma solidity 0.8.24; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +/** + * @dev Implementation of the ERC20 meant to be used in the context of double zero. + * + * Inside double zero the balance of each account is private and can only be checked by the user. + * This contract allows a user to publish that the own up to certain amount of the token. The amount + * is set by each user but can be queries by anyone. + */ contract PublicScopedBalanceErc20 is ERC20 { uint8 private _decimals; mapping(address => uint256) private puclicThresholds; @@ -13,20 +21,34 @@ contract PublicScopedBalanceErc20 is ERC20 { _decimals = 4; } + /** + * Mint. Meant to be used only by admins. Validated at double zero level. + */ function mint(address to, uint256 amount) public returns (bool) { _mint(to, amount); return true; } + /** + * Public + */ function decimals() public view override returns (uint8) { return _decimals; } + /** + * Anyone can query this. It returns if a user has or not more than an amunt set by + * that user. + */ function publicThreshold(address target) public view returns (uint256, bool) { uint256 threshold = puclicThresholds[target]; return (threshold, balanceOf(target) >= threshold); } + /** + * The double zero layer validates that this method can only be called with the users's + * own address. This allows the user to explose how much balance they allow to query. + */ function changePublicThreshold(uint256 newThreshold) external { puclicThresholds[msg.sender] = newThreshold; } diff --git a/src/PublicSelectionErc721.sol b/src/PublicSelectionErc721.sol index 2ee7b83..9d7ce31 100644 --- a/src/PublicSelectionErc721.sol +++ b/src/PublicSelectionErc721.sol @@ -5,6 +5,14 @@ pragma solidity 0.8.24; import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import {ERC721Enumerable} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; +/** + * @dev Implementation of the ERC20 meant to be used in the context of double zero. + * + * Inside double zero the iventory of each account is private and can only be checked by the user. + * This contract allows a user to publish take a subset of their inventory and make it public. + * This works because reading directly from contract's storage is not allowed in double zero. Also + * the double zero layer is used to prevent users to read from other users private data. + */ contract PublicSelectionErc721 is ERC721 { uint256 private _lastId; mapping(address owner => uint256[]) private publicInventories; @@ -13,6 +21,9 @@ contract PublicSelectionErc721 is ERC721 { _lastId = 0; } + /** + * Only for admins + */ function mintNext(address target) public returns (uint256) { _lastId += 1; @@ -22,10 +33,17 @@ contract PublicSelectionErc721 is ERC721 { return newItemId; } + /** + * Global access method. + */ function publicInventory(address owner) public view returns (uint256[] memory) { return publicInventories[owner]; } + /** + * Global access method. This cannot be used to filter private user data because on calls, + * double zero ensures that msg.sender is the logged in user. + */ function expose(uint256 tokenId) public { if (ownerOf(tokenId) != msg.sender) { revert(); @@ -42,19 +60,17 @@ contract PublicSelectionErc721 is ERC721 { publicInventories[msg.sender].push(tokenId); } + /** + * Public method. + */ function hide(uint256 tokenId) public { return _hideFor(tokenId, msg.sender); } - function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address) { - if (auth != address(0)) { - _hideFor(tokenId, auth); - } - - return super._update(to, tokenId, auth); - } - + /** + * Internal method. + */ function _hideFor(uint256 tokenId, address user) internal { uint256[] storage exposed = publicInventories[user]; for (uint i = 0; i < exposed.length; i++) { @@ -65,4 +81,15 @@ contract PublicSelectionErc721 is ERC721 { } } } + + /** + * Internal method. + */ + function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address) { + if (auth != address(0)) { + _hideFor(tokenId, auth); + } + + return super._update(to, tokenId, auth); + } } \ No newline at end of file diff --git a/src/ShareBalanceErc20.sol b/src/ShareBalanceErc20.sol index d9b2826..768a1df 100644 --- a/src/ShareBalanceErc20.sol +++ b/src/ShareBalanceErc20.sol @@ -4,6 +4,13 @@ pragma solidity 0.8.24; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +/** + * @dev Implementation of the ERC20 meant to be used in the context of double zero. + * + * Inside double zero the balance of each account is private and can only be checked by the user. + * This contracts allows users to share their balance only with specific users. + * This works because the double zero layer prevents other users to access data in a non intended way. + */ contract ShareBalanceErc20 is ERC20 { uint8 private _decimals; mapping(address => uint256) private puclicThresholds; @@ -14,15 +21,30 @@ contract ShareBalanceErc20 is ERC20 { _decimals = 4; } + /** + * Unrestricted method + */ function decimals() public view override returns (uint8) { return _decimals; } + /** + * Restricted only to admins + */ function mint(address _to, uint256 _amount) public returns (bool) { _mint(_to, _amount); return true; } + /** + * Unrestricted method. We control at the smart contract level that the + * sender has permission to read the balance. This works fine because the + * regular "balanceOf" method is restricted in the double zero layer to allow only + * each user to check their balance. + * + * This validation is not on the balanceOf method to avoid creating issues with existing smart + * contracts. + */ function authorizedBalanceOf(address account) public view returns (uint256) { if (account != msg.sender && !permissions[account][msg.sender]) { revert(); @@ -31,10 +53,16 @@ contract ShareBalanceErc20 is ERC20 { } + /** + * Unrestricted method. This doesn't leak any private info. + */ function shareBalanceWith(address reader) public { permissions[msg.sender][reader] = true; } + /** + * Unrestricted method. This doesn't leak any private info. + */ function hideBalanceFrom(address reader) public { permissions[msg.sender][reader] = false; } diff --git a/src/ShareScopedBalanceErc20.sol b/src/ShareScopedBalanceErc20.sol index 5e81ce0..05b6957 100644 --- a/src/ShareScopedBalanceErc20.sol +++ b/src/ShareScopedBalanceErc20.sol @@ -4,6 +4,15 @@ pragma solidity 0.8.24; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +/** + * @dev Implementation of the ERC20 meant to be used in the context of double zero. + * + * Inside double zero the balance of each account is private and can only be checked by the user. + * This contracts allows users to let specific accounts to check their balance, but scoped to a certain value. + * Alice decides to share their balance to Bob scoped to 100. Then when Bob checks Alice's balance 2 things might happen: + * - If Alice has less than 100 then Bob is going to see the real Alice balance. + * - If Alice has more than 100 then Bob is going to receive "100" as the response. + */ contract ShareScopedBalanceErc20 is ERC20 { uint8 private _decimals; mapping(address => uint256) private puclicThresholds; @@ -14,15 +23,27 @@ contract ShareScopedBalanceErc20 is ERC20 { _decimals = 4; } + /** + * Unrestructed method. + */ function decimals() public view override returns (uint8) { return _decimals; } + /** + * Restricted to admins. + */ function mint(address _to, uint256 _amount) public returns (bool) { _mint(_to, _amount); return true; } + /** + * Unrestructed method. This works because the original ERC20 balanceOf method + * is restricted in the double zero layer. Then users can only use this method to check othe users balance. + * We are not doing this logic in the ERC20 balanceOf method to avoid conflicts with existing + * smart contracts that use it. + */ function authorizedBalanceOf(address account) public view returns (uint256) { if (msg.sender == account) { return super.balanceOf(account); @@ -31,20 +52,30 @@ contract ShareScopedBalanceErc20 is ERC20 { } } - function min(uint256 n1, uint256 n2) internal pure returns (uint256) { - if (n1 <= n2) { - return n1; - } else { - return n2; - } - } + /** + * Unrestructed method. + */ function shareBalanceWith(address reader, uint256 amount) public { permissions[msg.sender][reader] = amount; } + /** + * Unrestructed method. + */ function hideBalanceFrom(address reader) public { permissions[msg.sender][reader] = 0; } + + /** + * internal. + */ + function min(uint256 n1, uint256 n2) internal pure returns (uint256) { + if (n1 <= n2) { + return n1; + } else { + return n2; + } + } }