Ondo's Cash protocol allows for whitelisted (KYC'd) user to hold exposure to Real World Assets (RWAs) on chain.
- Unit Testing - Foundry
- Fuzz Testing - Foundry
- Invariant Testing - Echidna
- Deployment & Scripting - Hardhat
Create an .env
file in this repositories root directory and populate the variables listed below
The RPC URLS can be generated by setting up free alchemy
ETHEREUM_RPC_URL='https://eth-mainnet.alchemyapi.io/v2/...'
GOERLI_RPC_URL='https://eth-goerli.g.alchemy.com/v2/...'
POLYGON_RPC_URL='https://polygon-mainnet.g.alchemy.com/v2/...'
MNEMONIC='test test test test ...'
FORK_FROM_BLOCK_NUMBER=15958078
ETHERSCAN_API_KEY=''
REPORT_GAS = true
MAINNET_PRIVATE_KEY=''
TESTNET_PRIVATE_KEY=''
FORGE_API_KEY_ETHEREUM = https://eth-mainnet.alchemyapi.io/v2/
Install foundry:
cargo install --git https://github.com/foundry-rs/foundry --profile local --locked foundry-cli anvil
To run unit tests:
yarn test-forge
To run an individual test file:
yarn test-forge --match-path <RELATIVE_PATH_TO_TEST_FILE>
To run tests and receive a gas report:
yarn test-forge --gas-report
Ensure that your .env
file is setup as described in, Env Setup and Testing
To start a Hardhat local node:
yarn local-node
Once the node is running you can run Hardhat scripts in a separate terminal to interact with and modify the state of the local blockchain. For example:
yarn hardhat run --network localhost scripts/ci/event_coverage.ts
The script scripts/ci/event_coverage.ts
aims to exercise the contracts such that all possible event types are emitted.
It is very useful for testing the subgraph and frontend, and it's a great example for how to write your own scripts.
The script is hardcoded to point to a node at http://127.0.0.1:8545
, which may need to be modified.
We use the hardhat-deploy community plugin for local node deployments and unit testing.
There are two sets of deployment files
-
local: Used for deploying to localhost
-
production: Used for deploying to mainnet
Each set of deployment files has a set of deploy scripts for each CASH instance (CASH
, CashKYCSender
, CashKYCSenderReceiver
). Each of these instances deploys a CASH
token and a corresponding CashManager
. The latter two tokens (CashKYCSender
, CashKYCSenderReceiver
) must be deployed after the kycRegistry
is deployed. After deployment, each CASH instance has a corresponding post deploy script (deploy_cash
, deploy_cashKYCSender
, deploy_cashKYCSenderReceiver
) which permissions roles on Defender and deploys tokens from the factory contracts.
For testing with Foundry, forge-tests/BasicDeployment.sol
was added to allow for users to easily deploy and setup the CASH/CASH+ dapp for local testing.
To test contracts within foundry from a deployed state please include the following layout within your testing file.
pragma solidity 0.8.16;
import "forge-tests/BasicDeployment.sol";
contract TestCashManager_Cash is BasicDeployment {
function setUp() public {
createDeploymentCash();
}
function testName() public {
console.log(cashProxied.name());
}
}
Install Echidna here
To run tests:
yarn clean && yarn compile && echidna-test . --contract E2E --config contracts/echidna/config.yaml
Ondo's KYC Registry has an external function, addKYCAddressViaSignature
, that
allows an external actor to add an user to the KYC Registry for
a specific group. This function takes in a signature of a message digest computed from the attributes {user, KYC requirement group, deadline} and verifies that it has been signed by a wallet that has been whitelisted in the access control functionality
of the KYC Registry.
Please see the sample typescript code for how message can be computed in order to create a signature that the KYC Registry can validate and allow external actors to add to KYC registry.
const domain = {
name: "OndoKYCRegistry",
version: "1",
chainId: 1,
verifyingContract: "<KYCRegistry Address>",
};
// The named list of all type definitions
const types = {
KYCApproval: [
{ name: "kycRequirementGroup", type: "uint256" },
{ name: "user", type: "address" },
{ name: "deadline", type: "uint256" },
],
};
// The data to sign
const value = {
kycRequirementGroup: "<group number>",
user: "<user address to add>",
deadline: "500",
};
console.log(ethers.utils._TypedDataEncoder.hash(domain, types, value));
The output of the above is an EIP 712 compliant digest. We will use an OZ defender relayer as the signer. (See https://docs.openzeppelin.com/defender/relay-api-reference#sign-typed-data-endpoint for more). A successful signature will result in a response similar to:
{'r': '0xbedbf96ab9d59ad2178d8fcb18588897c854f92a065f0347925f1387b4c38fe7',
's': '0x72ca0c2d966ff214800d80c74f4301bf6230bcc95d488470bb1da8ee1f76c0f6',
'v': 37,
...}
Please note that ecrecover (which the Registry uses) requires V be 27 or 28 So a conversion must be applied before interacting with addKYCAddressViaSignature
Python must be installed. Open a terminal and run:
pip3 install slither-analyzer
To run slither on a specific contract run the following in terminal:
yarn slither-check contracts/CashManager.sol