From 2855da31338ae8785703ac3d27472e6a50096a42 Mon Sep 17 00:00:00 2001 From: Will Hester Date: Mon, 23 Feb 2026 15:50:02 +0000 Subject: [PATCH] refine sc docs --- .../1-data-access.md | 40 +++++++++---------- .../2-native-entropy.md | 2 +- .../3-native-commit-reveal.md | 19 +++++---- .../4-transaction-timestamp.md | 32 ++++++++++----- 4 files changed, 51 insertions(+), 42 deletions(-) diff --git a/docs/3-smart-contract-features/1-data-access.md b/docs/3-smart-contract-features/1-data-access.md index 3ddbc8c6..6395dd7d 100644 --- a/docs/3-smart-contract-features/1-data-access.md +++ b/docs/3-smart-contract-features/1-data-access.md @@ -14,9 +14,9 @@ To achieve this, we had to: 1. Run the Ethereum Virtual Machine inside a Trusted Execution Environment (TEE) and store the state in encrypted storage. This prevents the node operator from accessing the data. 2. Disable the `getStorageAt` method, so now contracts can have secrets (as long as the `private` state variables are not exposed via public view functions). This prevents anyone with RPC access to the node from reading the data. -3. Authenticate "view function calls" so that the smart contract knows who is calling the view function. Without this feature there is no "Data Access **Control**", because the dApp developer can't write access control logic. -4. Event logs are another way to expose data from a contract to the outside world. A practical platform needs a way to configure who can read the various event logs. -5. Control access to the "Transaction Receipts", which contain the logs and status of the transaction. +3. Authenticate "view function calls" so that the smart contract knows who is calling the view function. Without this feature, there is no "Data Access **Control**", because the dApp developer can't write access control logic. +4. Configure event log visibility, since event logs are another way to expose data from a contract to the outside world. A practical platform needs a way to configure who can read the various event logs. +5. Control access to the "Transaction Receipts", which contain the logs and status of the transaction. ## Data Access Control Rules @@ -34,14 +34,13 @@ Here, we'll list the platform rules. The examples below will showcase how exactl - everyone, if the transaction called a transparent contract. - `eth_getStorageAt` can be called on "transparent" contracts. - ## Data Access Control Example Let's illustrate with a basic storage dApp example where users can store and retrieve a number. At every step, we'll add a new feature and explain the difference between `TEN` and `Ethereum`. -### Step 1: Basic contract with a Public Variable +### Step 1: Basic Contract with a Public Variable #### Code @@ -60,14 +59,13 @@ contract StorageExample { #### Explanation -In this step, we created a public variable `storedValues` that maps the provided value to the address of the user who called the `storeValue` function. +In this step, we created a public variable `storedValues` that maps each user's address to their stored value. Because the variable is public, Solidity will provide a default public getter for it. Since there are no data access restrictions, on both Ethereum and TEN, everyone will be able to read the values of all users by just calling the default public getter. - -### Step 2: Converting to a Private Variable with an explicit Getter Function +### Step 2: Converting to a Private Variable with an Explicit Getter Function #### Code @@ -78,7 +76,7 @@ contract StorageExample { function storeValue(uint256 value) public { _storedValues[tx.origin] = value; } - + function getValue(address account) public view returns (uint256) { return _storedValues[account]; } @@ -87,10 +85,10 @@ contract StorageExample { #### Explanation -The `storedValues` variable is now private, and we added a basic `getValue` function for users to retrieve their value. +The `_storedValues` variable is now private, and we added a basic `getValue` function for users to retrieve their value. -On both Ethereum and TEN, anyone can call `getValue` to retrieve any value. -On Ethereum, `_storedValues` can also be accessed directly with `getStorageAt` +On both Ethereum and TEN, anyone can call `getValue` to retrieve any value. +On Ethereum, `_storedValues` can also be accessed directly with `getStorageAt`. ### Step 3: Data Access Control @@ -115,18 +113,17 @@ contract StorageExample { #### Explanation -The key line is: ``require(tx.origin == account, "Not authorised!");``, which ensures that the caller of the view function is the owner of the data. +The key line is: `require(tx.origin == account, "Not authorised!");`, which ensures that the caller of the view function is the owner of the data. **When deployed on TEN, this code guarantees that all users can only access their own values, and nobody can read the `_storedValues`.** On Ethereum, the `tx.origin` is not authenticated, so the check above is not effective and `eth_getStorageAt` is available. - ### Step 4: Emitting Events - Default Visibility Event logs notify UIs about state changes in smart contracts. -To improve our smart contract, we’ll emit an event when a user stores a value and milestone events when a specific size threshold is met. +To improve our smart contract, we’ll emit an event when a user stores a value and a milestone event when a call count threshold is reached. #### Code @@ -158,18 +155,18 @@ contract StorageExample { Notice how we defined the two events: `DataChanged` and `MilestoneReached`, and are emitting them in the `storeValue` function. -In Ethereum, everyone can query and subscribe to these events. If this was possible on TEN, it would completely break the functionality because you can see all the secret values. +In Ethereum, everyone can query and subscribe to these events. If this were possible on TEN, it would completely break the functionality because anyone could see all the secret values. -Notice how in this version, we have no configuration for event log visibility, so we are relying on the default rules: +Notice how in this version, we have no configuration for event log visibility, so we are relying on the [default rules](#data-access-control-rules): -- Rule 1: Event logs that contain ("Externally owned Account") EOAs as indexed fields (topics) are only visible to those EOAs. +- Rule 1: Event logs that contain EOAs as topics are only visible to those EOAs. - Rule 2: Event logs that don't contain any EOA are visible to everyone. In our case, the default rules ensure that: + - `DataChanged` is visible only to the address that is storing the value. - `MilestoneReached` is publicly visible. - ### Step 5: Customising Event Visibility The default visibility rules are a good starting point, but complex dApps require greater flexibility. @@ -245,10 +242,11 @@ contract StorageExample is ContractTransparencyConfig { The [`ContractTransparencyConfig`](https://github.com/ten-protocol/go-ten/blob/main/contracts/src/system/config/IContractTransparencyConfig.sol) interface is known by the TEN platform. When a contract is deployed, the platform will call the `visibilityRules` function, and store the `VisibilityConfig`. -For each event type, you can configure which fields can access it. +For each event type, you can configure who can view it based on its topics (indexed fields). -Notice how in the `visibilityRules` above, we configure the `DataChanged` event to be visible to the first field and the sender, and the `MilestoneReached` to be visible to everyone. +Notice how in the `visibilityRules` above, we configure the `DataChanged` event to be visible to the first topic and the sender, and the `MilestoneReached` to be visible to everyone. The other configuration: `VisibilityConfig.contractCfg` applies to the entire contract: + - `ContractCfg.TRANSPARENT`: The contracts will have public storage and events, behaving exactly like Ethereum. - `ContractCfg.PRIVATE`: The default TEN behaviour, where the storage is not accessible and the events are individually configurable. diff --git a/docs/3-smart-contract-features/2-native-entropy.md b/docs/3-smart-contract-features/2-native-entropy.md index ac7e231d..df4d8ae6 100644 --- a/docs/3-smart-contract-features/2-native-entropy.md +++ b/docs/3-smart-contract-features/2-native-entropy.md @@ -9,7 +9,7 @@ This excellent [blog](https://medium.com/obscuro-labs/against-all-odds-securing- ## How it works TEN provides a "System Contract" (a contract deployed and known by the platform.) -You can get the address of the system contract for our testnet [here](https://sepolia.tenscan.io/resources/verified-data) - "??". +You can get the address of the system contract for our testnet [here](https://sepolia.tenscan.io/resources/verified-data) - "TEN System Contract". The interface you must implement is: diff --git a/docs/3-smart-contract-features/3-native-commit-reveal.md b/docs/3-smart-contract-features/3-native-commit-reveal.md index 0c23c8ec..c7e9400b 100644 --- a/docs/3-smart-contract-features/3-native-commit-reveal.md +++ b/docs/3-smart-contract-features/3-native-commit-reveal.md @@ -6,11 +6,11 @@ sidebar_position: 3 Every on-chain game developer knows that moves that rely on entropy must be executed in two steps. -Imagine you implement an on-chain coin flip game. The player pays 0.1ETH to choose `Heads` or `Tails`. -If they win, they receive 0.2ETH, otherwise they lose the 0.1ETH. +Imagine you implement an on-chain coin flip game. The player pays 0.1 ETH to choose `Heads` or `Tails`. +If they win, they receive 0.2 ETH, otherwise they lose the 0.1 ETH. Even if randomness is unpredictable, this simple game can be exploited in several ways: -- The attacker can create a “proxy” smart contract to play on their behalf. Using a similar mechanism to flash loans in DeFi: the proxy is programmed to make multiple actions and only “commit” if it can obtain a profit. In our case, if the coin flip is losing, the proxy can just revert. The only cost will be the gas burned. +- The attacker can create a “proxy” smart contract to play on their behalf. Using a mechanism similar to flash loans in DeFi: the proxy is programmed to make multiple actions and only “commit” if it can obtain a profit. In our case, if the coin flip is losing, the proxy can just revert. The only cost will be the gas burned. - Transactions consume gas, and the gas cost can inadvertently reveal information. For instance, if a winning move is more computationally intensive than a losing one, players could deduce optimal moves by estimating gas costs for various actions. The typical solution is to use an ad-hoc commit-reveal scheme. The smart contract ensures that the player commits to a move, and only afterwards reveals it to the chain. @@ -28,8 +28,8 @@ To avoid increasing the latency, the move must be executed in the same block as ### How it works -TEN provides a "System Contract" (a contract deployed and known by the platform.) -You can get the address of the system contract for our testnet [here](https://sepolia.tenscan.io/resources/verified-data) - "Ten System Contract". +TEN provides a "System Contract" (a contract deployed and known by the platform). +You can get the address of the system contract for our testnet [here](https://sepolia.tenscan.io/resources/verified-data) - "Ten Callbacks". The interface for registering the callback is: [IPublicCallbacks](https://github.com/ten-protocol/go-ten/blob/main/contracts/src/system/interfaces/IPublicCallbacks.sol). @@ -50,7 +50,7 @@ contract CoinFlip { // Event to emit the result of the coin flip event CoinFlipResult(address indexed player, bool didWin, uint256 randomNumber); - private IPublicCallbacks tenCallbacks; + IPublicCallbacks private tenCallbacks; mapping(uint256 callbackId => address player) public callbackToPlayer; mapping(address player => uint256 refundAmount) public playerToRefundAmount; @@ -61,7 +61,7 @@ contract CoinFlip { // you have to pass in the address of the system contract constructor(address _tenCallbacksAddress) { - tenCallbacks = TenCallbacks(_tenCallbacksAddress); + tenCallbacks = IPublicCallbacks(_tenCallbacksAddress); } // Function to initiate a coin flip. @@ -70,8 +70,7 @@ contract CoinFlip { // Assume doFlipCoin costs 50_000 gas; // We deduct a predetermined amount from the bet to pay for delayed execution. uint256 etherGasForCoinFlip = 50_000*block.basefee; - require(msg.value > etherGasForCoinFlip, "Insufficent gas"); - + require(msg.value > etherGasForCoinFlip, "Insufficient gas"); // Encode the function we want to be called by the TEN system contract. bytes memory callbackTargetInfo = abi.encodeWithSelector(this.doFlipCoin.selector, msg.sender, msg.value - etherGasForCoinFlip, isHeads); @@ -94,7 +93,7 @@ contract CoinFlip { require(success, "Payment failed."); } // Emit the result of the coin flip - emit CoinFlipResult(msg.sender, isHeads, randomNumber); + emit CoinFlipResult(bettor, wantsHeads == isHeads, randomNumber); } function getRandomNumber() internal view returns (uint256) { diff --git a/docs/3-smart-contract-features/4-transaction-timestamp.md b/docs/3-smart-contract-features/4-transaction-timestamp.md index 9d726033..14977006 100644 --- a/docs/3-smart-contract-features/4-transaction-timestamp.md +++ b/docs/3-smart-contract-features/4-transaction-timestamp.md @@ -5,7 +5,7 @@ sidebar_position: 4 # Precise transaction timestamp Real-time games require users to make quick decisions, and the outcomes depend on the precise moment in time when the action was made. -This doesn't work well on-chain because latencies are not low enough. **The timestamp is provided in milliseconds**. +This doesn't work well on-chain because latencies are not low enough. **The TEN timestamp is provided in milliseconds**. ### Option 1 - External Timestamp oracle @@ -26,10 +26,10 @@ Each transaction is accompanied by the precise timestamp when included in a bloc ## How it works -TEN provides a "System Contract" (a contract deployed and known by the platform.) -You can get the address of the system contract for our testnet [here](https://sepolia.tenscan.io/resources/verified-data) - under `TenSystemCalls`. +TEN provides a "System Contract" (a contract deployed and known by the platform). +You can get the address of the system contract for our testnet [here](https://sepolia.tenscan.io/resources/verified-data) - "TEN System Contract". -The interface you must implement is: +The interface for the system contract is: ```solidity interface ITimestamp { @@ -47,16 +47,28 @@ interface ITimestamp { function getTransactionTimestamp() external returns (uint256); } -contract CoinFlip { - private ITimestamp timestamp; +contract TimedAuction { + ITimestamp private timestamp; - // you have to pass in the address of the system contract - constructor(address _timestampSystemAddress) { + uint256 public auctionEndMs; + address public highestBidder; + uint256 public highestBid; + + event BidPlaced(address indexed bidder, uint256 amount, uint256 timestampMs); + + constructor(address _timestampSystemAddress, uint256 _durationMs) { timestamp = ITimestamp(_timestampSystemAddress); + auctionEndMs = timestamp.getTransactionTimestamp() + _durationMs; } - function getRandomNumber() internal view returns (uint256) { - return rnd.getRandomNumber(); + function bid() external payable { + uint256 txTimestamp = timestamp.getTransactionTimestamp(); + require(txTimestamp < auctionEndMs, "Auction has ended"); + require(msg.value > highestBid, "Bid too low"); + + highestBidder = msg.sender; + highestBid = msg.value; + emit BidPlaced(msg.sender, msg.value, txTimestamp); } } ``` \ No newline at end of file