Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 19 additions & 21 deletions docs/3-smart-contract-features/1-data-access.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand All @@ -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

Expand All @@ -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];
}
Expand All @@ -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

Expand All @@ -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

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
2 changes: 1 addition & 1 deletion docs/3-smart-contract-features/2-native-entropy.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
19 changes: 9 additions & 10 deletions docs/3-smart-contract-features/3-native-commit-reveal.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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).

Expand All @@ -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;

Expand All @@ -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.
Expand All @@ -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);

Expand All @@ -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) {
Expand Down
32 changes: 22 additions & 10 deletions docs/3-smart-contract-features/4-transaction-timestamp.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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 {
Expand All @@ -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);
}
}
```