-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
196 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,3 +8,7 @@ | |
.pytest_cache | ||
.python-version | ||
__pycache__ | ||
|
||
# Forge | ||
cache/ | ||
out/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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/) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
|
||
} |