Skip to content

Commit a2b62a1

Browse files
authored
Merge pull request #105 from ten-protocol/will/smart-contract-review
Review of the smart contract features
2 parents b499dd3 + 2855da3 commit a2b62a1

4 files changed

Lines changed: 51 additions & 42 deletions

File tree

docs/3-smart-contract-features/1-data-access.md

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ To achieve this, we had to:
1414

1515
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.
1616
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.
17-
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.
18-
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.
19-
5. Control access to the "Transaction Receipts", which contain the logs and status of the transaction.
17+
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.
18+
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.
19+
5. Control access to the "Transaction Receipts", which contain the logs and status of the transaction.
2020

2121
## Data Access Control Rules
2222

@@ -34,14 +34,13 @@ Here, we'll list the platform rules. The examples below will showcase how exactl
3434
- everyone, if the transaction called a transparent contract.
3535
- `eth_getStorageAt` can be called on "transparent" contracts.
3636

37-
3837
## Data Access Control Example
3938

4039
Let's illustrate with a basic storage dApp example where users can store and retrieve a number.
4140

4241
At every step, we'll add a new feature and explain the difference between `TEN` and `Ethereum`.
4342

44-
### Step 1: Basic contract with a Public Variable
43+
### Step 1: Basic Contract with a Public Variable
4544

4645
#### Code
4746

@@ -60,14 +59,13 @@ contract StorageExample {
6059

6160
#### Explanation
6261

63-
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.
62+
In this step, we created a public variable `storedValues` that maps each user's address to their stored value.
6463

6564
Because the variable is public, Solidity will provide a default public getter for it.
6665

6766
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.
6867

69-
70-
### Step 2: Converting to a Private Variable with an explicit Getter Function
68+
### Step 2: Converting to a Private Variable with an Explicit Getter Function
7169

7270
#### Code
7371

@@ -78,7 +76,7 @@ contract StorageExample {
7876
function storeValue(uint256 value) public {
7977
_storedValues[tx.origin] = value;
8078
}
81-
79+
8280
function getValue(address account) public view returns (uint256) {
8381
return _storedValues[account];
8482
}
@@ -87,10 +85,10 @@ contract StorageExample {
8785

8886
#### Explanation
8987

90-
The `storedValues` variable is now private, and we added a basic `getValue` function for users to retrieve their value.
88+
The `_storedValues` variable is now private, and we added a basic `getValue` function for users to retrieve their value.
9189

92-
On both Ethereum and TEN, anyone can call `getValue` to retrieve any value.
93-
On Ethereum, `_storedValues` can also be accessed directly with `getStorageAt`
90+
On both Ethereum and TEN, anyone can call `getValue` to retrieve any value.
91+
On Ethereum, `_storedValues` can also be accessed directly with `getStorageAt`.
9492

9593
### Step 3: Data Access Control
9694

@@ -115,18 +113,17 @@ contract StorageExample {
115113

116114
#### Explanation
117115

118-
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.
116+
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.
119117

120118
**When deployed on TEN, this code guarantees that all users can only access their own values, and nobody can read the `_storedValues`.**
121119

122120
On Ethereum, the `tx.origin` is not authenticated, so the check above is not effective and `eth_getStorageAt` is available.
123121

124-
125122
### Step 4: Emitting Events - Default Visibility
126123

127124
Event logs notify UIs about state changes in smart contracts.
128125

129-
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.
126+
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.
130127

131128
#### Code
132129

@@ -158,18 +155,18 @@ contract StorageExample {
158155

159156
Notice how we defined the two events: `DataChanged` and `MilestoneReached`, and are emitting them in the `storeValue` function.
160157

161-
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.
158+
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.
162159

163-
Notice how in this version, we have no configuration for event log visibility, so we are relying on the default rules:
160+
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):
164161

165-
- Rule 1: Event logs that contain ("Externally owned Account") EOAs as indexed fields (topics) are only visible to those EOAs.
162+
- Rule 1: Event logs that contain EOAs as topics are only visible to those EOAs.
166163
- Rule 2: Event logs that don't contain any EOA are visible to everyone.
167164

168165
In our case, the default rules ensure that:
166+
169167
- `DataChanged` is visible only to the address that is storing the value.
170168
- `MilestoneReached` is publicly visible.
171169

172-
173170
### Step 5: Customising Event Visibility
174171

175172
The default visibility rules are a good starting point, but complex dApps require greater flexibility.
@@ -245,10 +242,11 @@ contract StorageExample is ContractTransparencyConfig {
245242
The [`ContractTransparencyConfig`](https://github.com/ten-protocol/go-ten/blob/main/contracts/src/system/config/IContractTransparencyConfig.sol) interface is known by the TEN platform.
246243
When a contract is deployed, the platform will call the `visibilityRules` function, and store the `VisibilityConfig`.
247244

248-
For each event type, you can configure which fields can access it.
245+
For each event type, you can configure who can view it based on its topics (indexed fields).
249246

250-
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.
247+
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.
251248

252249
The other configuration: `VisibilityConfig.contractCfg` applies to the entire contract:
250+
253251
- `ContractCfg.TRANSPARENT`: The contracts will have public storage and events, behaving exactly like Ethereum.
254252
- `ContractCfg.PRIVATE`: The default TEN behaviour, where the storage is not accessible and the events are individually configurable.

docs/3-smart-contract-features/2-native-entropy.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ This excellent [blog](https://medium.com/obscuro-labs/against-all-odds-securing-
99
## How it works
1010

1111
TEN provides a "System Contract" (a contract deployed and known by the platform.)
12-
You can get the address of the system contract for our testnet [here](https://sepolia.tenscan.io/resources/verified-data) - "??".
12+
You can get the address of the system contract for our testnet [here](https://sepolia.tenscan.io/resources/verified-data) - "TEN System Contract".
1313

1414
The interface you must implement is:
1515

docs/3-smart-contract-features/3-native-commit-reveal.md

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ sidebar_position: 3
66

77
Every on-chain game developer knows that moves that rely on entropy must be executed in two steps.
88

9-
Imagine you implement an on-chain coin flip game. The player pays 0.1ETH to choose `Heads` or `Tails`.
10-
If they win, they receive 0.2ETH, otherwise they lose the 0.1ETH.
9+
Imagine you implement an on-chain coin flip game. The player pays 0.1 ETH to choose `Heads` or `Tails`.
10+
If they win, they receive 0.2 ETH, otherwise they lose the 0.1 ETH.
1111
Even if randomness is unpredictable, this simple game can be exploited in several ways:
1212

13-
- 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.
13+
- 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.
1414
- 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.
1515

1616
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
2828

2929
### How it works
3030

31-
TEN provides a "System Contract" (a contract deployed and known by the platform.)
32-
You can get the address of the system contract for our testnet [here](https://sepolia.tenscan.io/resources/verified-data) - "Ten System Contract".
31+
TEN provides a "System Contract" (a contract deployed and known by the platform).
32+
You can get the address of the system contract for our testnet [here](https://sepolia.tenscan.io/resources/verified-data) - "Ten Callbacks".
3333

3434
The interface for registering the callback is: [IPublicCallbacks](https://github.com/ten-protocol/go-ten/blob/main/contracts/src/system/interfaces/IPublicCallbacks.sol).
3535

@@ -50,7 +50,7 @@ contract CoinFlip {
5050
// Event to emit the result of the coin flip
5151
event CoinFlipResult(address indexed player, bool didWin, uint256 randomNumber);
5252
53-
private IPublicCallbacks tenCallbacks;
53+
IPublicCallbacks private tenCallbacks;
5454
mapping(uint256 callbackId => address player) public callbackToPlayer;
5555
mapping(address player => uint256 refundAmount) public playerToRefundAmount;
5656
@@ -61,7 +61,7 @@ contract CoinFlip {
6161
6262
// you have to pass in the address of the system contract
6363
constructor(address _tenCallbacksAddress) {
64-
tenCallbacks = TenCallbacks(_tenCallbacksAddress);
64+
tenCallbacks = IPublicCallbacks(_tenCallbacksAddress);
6565
}
6666
6767
// Function to initiate a coin flip.
@@ -70,8 +70,7 @@ contract CoinFlip {
7070
// Assume doFlipCoin costs 50_000 gas;
7171
// We deduct a predetermined amount from the bet to pay for delayed execution.
7272
uint256 etherGasForCoinFlip = 50_000*block.basefee;
73-
require(msg.value > etherGasForCoinFlip, "Insufficent gas");
74-
73+
require(msg.value > etherGasForCoinFlip, "Insufficient gas");
7574
// Encode the function we want to be called by the TEN system contract.
7675
bytes memory callbackTargetInfo = abi.encodeWithSelector(this.doFlipCoin.selector, msg.sender, msg.value - etherGasForCoinFlip, isHeads);
7776
@@ -94,7 +93,7 @@ contract CoinFlip {
9493
require(success, "Payment failed.");
9594
}
9695
// Emit the result of the coin flip
97-
emit CoinFlipResult(msg.sender, isHeads, randomNumber);
96+
emit CoinFlipResult(bettor, wantsHeads == isHeads, randomNumber);
9897
}
9998
10099
function getRandomNumber() internal view returns (uint256) {

docs/3-smart-contract-features/4-transaction-timestamp.md

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ sidebar_position: 4
55
# Precise transaction timestamp
66

77
Real-time games require users to make quick decisions, and the outcomes depend on the precise moment in time when the action was made.
8-
This doesn't work well on-chain because latencies are not low enough. **The timestamp is provided in milliseconds**.
8+
This doesn't work well on-chain because latencies are not low enough. **The TEN timestamp is provided in milliseconds**.
99

1010
### Option 1 - External Timestamp oracle
1111

@@ -26,10 +26,10 @@ Each transaction is accompanied by the precise timestamp when included in a bloc
2626

2727
## How it works
2828

29-
TEN provides a "System Contract" (a contract deployed and known by the platform.)
30-
You can get the address of the system contract for our testnet [here](https://sepolia.tenscan.io/resources/verified-data) - under `TenSystemCalls`.
29+
TEN provides a "System Contract" (a contract deployed and known by the platform).
30+
You can get the address of the system contract for our testnet [here](https://sepolia.tenscan.io/resources/verified-data) - "TEN System Contract".
3131

32-
The interface you must implement is:
32+
The interface for the system contract is:
3333

3434
```solidity
3535
interface ITimestamp {
@@ -47,16 +47,28 @@ interface ITimestamp {
4747
function getTransactionTimestamp() external returns (uint256);
4848
}
4949
50-
contract CoinFlip {
51-
private ITimestamp timestamp;
50+
contract TimedAuction {
51+
ITimestamp private timestamp;
5252
53-
// you have to pass in the address of the system contract
54-
constructor(address _timestampSystemAddress) {
53+
uint256 public auctionEndMs;
54+
address public highestBidder;
55+
uint256 public highestBid;
56+
57+
event BidPlaced(address indexed bidder, uint256 amount, uint256 timestampMs);
58+
59+
constructor(address _timestampSystemAddress, uint256 _durationMs) {
5560
timestamp = ITimestamp(_timestampSystemAddress);
61+
auctionEndMs = timestamp.getTransactionTimestamp() + _durationMs;
5662
}
5763
58-
function getRandomNumber() internal view returns (uint256) {
59-
return rnd.getRandomNumber();
64+
function bid() external payable {
65+
uint256 txTimestamp = timestamp.getTransactionTimestamp();
66+
require(txTimestamp < auctionEndMs, "Auction has ended");
67+
require(msg.value > highestBid, "Bid too low");
68+
69+
highestBidder = msg.sender;
70+
highestBid = msg.value;
71+
emit BidPlaced(msg.sender, msg.value, txTimestamp);
6072
}
6173
}
6274
```

0 commit comments

Comments
 (0)