Skip to content

Commit

Permalink
Switching to Forge
Browse files Browse the repository at this point in the history
  • Loading branch information
miohtama committed Jan 10, 2024
1 parent 4278a3a commit 0053ce2
Show file tree
Hide file tree
Showing 3 changed files with 196 additions and 35 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@
.pytest_cache
.python-version
__pycache__

# Forge
cache/
out/
117 changes: 82 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,66 +1,113 @@
## Foundry
# Terms Of Service Acceptance Manager

**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**
**Note**: This project is still under initial development,
and not ready yet.

Foundry consists of:
A Solidity smart contract for making sure the smart contract caller
has accepted the latest terms of service.

- **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.
## Use cases

## Documentation
- Record on-chain that users have accepted some sort of a disclaimer
- Enforce users to accept disclaimers when they use with the smart contract
- User signs an [EIP-191 message](https://eips.ethereum.org/EIPS/eip-191) from their wallet
- Multisignature wallet and protocol friendly [EIP-1271](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/SignatureChecker.sol) is supported

https://book.getfoundry.sh/
## Requirements

## Usage
- Python 3.10+

### Build
## Workflow

```shell
$ forge build
```
### Deploying

### Test
Deploy [TermsOfService smart contract](./contracts/TermsOfService.sol) for your chain

```shell
$ forge test
```
- Each deployment has its own smart contract address
- Each chain needs its own deployment
- Each TermsOfService tracks the currently active terms of service text

### Format
### Updating terms of service

Creating terms of service

- Create a Markdown/plain text terms of service file
- Must be dated
- Must have a version number counter start from 1, then 2
- Record SHA-256 bit hash of the text
- Use update script to bump the new terms of service live

### Users to sign the terms of service

- The smart contract has `canProceed` function to check if a
particular address has signed the latest terms of service version
- The user signs a [template message](./terms_of_service/acceptance_message.py)
with their wallet. Note that this message only refers to the actual
terms of service based on its version, hash, date and link,
- The address must have always signed the latest terms of service,
and should be prompted to sign again if this is not the case
- The terms of service signing payload can be passed part
as another smart contract transaction, and **does not** need
to be a separate transaction

On hashes: There are two hashes. One for the actual terms of service
file (never referred in the smart contracts) and one for the message
(template-based) that users need to sign with their wallet.

## Getting started

Install framework with Poetry:

```shell
$ forge fmt
```
poetry install
```

### Gas Snapshots
## Compiling

```shell
$ forge snapshot
poetry shell
ape compile
```

### Anvil
## Running tests

```shell
$ anvil
poetry shell
ape test
```

### Deploy
## Deploying

Using Foundry.

Compile:

```shell
$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>
forge build
```

### Cast
Then:

```shell
$ cast <subcommand>
```shell
export DEPLOY_PRIVATE_KEY=
export JSON_RPC_POLYGON=
forge create --rpc-url $JSON_RPC_POLYGON --private-key $DEPLOY_PRIVATE_KEY contracts/TermsOfService.sol:TermsOfService
```

### Help
### Initialising

The following placeholder terms of service message is used.

```shell
$ forge --help
$ anvil --help
$ cast --help
```
```



## More information

- [Join Discord for any questions](https://tradingstrategy.ai/community).
- [Watch tutorials on YouTube](https://www.youtube.com/@tradingstrategyprotocol)
- [Follow on Twitter](https://twitter.com/TradingProtocol)
- [Follow on Telegram](https://t.me/trading_protocol)
- [Follow on LinkedIn](https://www.linkedin.com/company/trading-strategy/)
110 changes: 110 additions & 0 deletions src/TermsOfService.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.0;

import "@openzeppelin/access/Ownable.sol";

// Support https://eips.ethereum.org/EIPS/eip-1271
// verification of smart contract signatures
import "@openzeppelin/utils/cryptography/SignatureChecker.sol";

/**
* Terms of service acceptance tracker
*
* Manage signatures of users of different versions of terms of service.
*/
contract TermsOfService is Ownable {

using SignatureChecker for address;

// Terms of service acceptances
//
// Account can and may need to accept multiple terms of services.
// Each terms of service is identified by its hash of text.
// The acceptance is a message that signs this terms of service version.
//
mapping(address account => mapping(bytes32 acceptanceMessageHash => bool accepted)) public acceptances;

//
// Published terms of services
//
mapping(uint16 version => bytes32 acceptanceMessageHash) public versions;

bytes32 public latestAcceptanceMessageHash;

//
// Terms of service versions, starting from 1 and
// increased with one for the each iteration.
//
uint16 public latestTermsOfServiceVersion;

// Add a new terms of service version
event UpdateTermsOfService(uint16 version, bytes32 acceptanceMessageHash);

event Signed(address signer, uint16 version, bytes32 hash, bytes metadata);

constructor() Ownable() {
}

function hasAcceptedHash(address account, bytes32 acceptanceMessageHash) public view returns (bool accepted) {
return acceptances[account][acceptanceMessageHash];
}

function getTextHash(uint16 version) public view returns (bytes32 hash) {
return versions[version];
}

function hasAcceptedVersion(address account, uint16 version) public view returns (bool accepted) {
bytes32 hash = versions[version];
require(hash != bytes32(0), "No such version");
return hasAcceptedHash(account, hash);
}

function updateTermsOfService(uint16 version, bytes32 acceptanceMessageHash) public onlyOwner {
require(version == latestTermsOfServiceVersion + 1, "Versions must be updated incrementally");
require(acceptanceMessageHash != latestAcceptanceMessageHash, "Setting the same terms of service twice");
latestAcceptanceMessageHash = acceptanceMessageHash;
latestTermsOfServiceVersion = version;
versions[version] = acceptanceMessageHash;
emit UpdateTermsOfService(version, acceptanceMessageHash);
}

/**
* Can the current user proceed to the next step, or they they need to sign
* the latest terms of service.
*/
function canProceed() public view returns (bool accepted) {
require(latestAcceptanceMessageHash != bytes32(0), "Terms of service not initialised");
return hasAcceptedHash(msg.sender, latestAcceptanceMessageHash);
}

/**
* Sign terms of service
*
* - Externally Owned Account sign
* - EIP-1271 sign
* - EIP-191 formatted message
*
* The user can sign multiple times.
*
* See
*
* - ECDSA tryRecover https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ECDSA.sol
*
* - Gnosis Safe signing example: https://github.com/safe-global/safe-eth-py/blob/master/gnosis/safe/tests/test_safe_signature.py#L195
*
* - OpenZeppelin SignatureChecker implementation: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/SignatureChecker.sol
*/
function signTermsOfServiceBehalf(address signer, bytes32 hash, bytes memory signature, bytes memory metadata) public {
require(hash == latestAcceptanceMessageHash, "Cannot sign older or unknown versions terms of services");
require(signer.isValidSignatureNow(hash, signature), "Signature is not valid");
require(acceptances[signer][latestAcceptanceMessageHash] == false, "Already signed");
acceptances[signer][latestAcceptanceMessageHash] = true;
emit Signed(signer, latestTermsOfServiceVersion, latestAcceptanceMessageHash, metadata);
}

function signTermsOfServiceOwn(bytes32 hash, bytes memory signature, bytes memory metadata) public {
signTermsOfServiceBehalf(msg.sender, hash, signature, metadata);
}

}

0 comments on commit 0053ce2

Please sign in to comment.