diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index fb27638d..bc853527 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -33,13 +33,17 @@ jobs: - name: Install yarn dependencies run: yarn install --no-progress --non-interactive - - name: Set up python + - name: Set up Python 3.7 uses: actions/setup-python@v2 with: python-version: 3.7 - - name: Install python dependencies - run: pip install -r requirements.txt - - - name: Run linters - run: yarn check-format + - name: Install dependencies + run: | + sudo apt install -y libgmp3-dev + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Run Linters + run: | + yarn check-format diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7e719bd6..98a8d5f0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -29,26 +29,26 @@ jobs: key: yarn-${{ hashFiles('**/yarn.lock') }} restore-keys: | yarn- - - name: Install yarn dependencies run: yarn install --no-progress --non-interactive + - name: Set up Python 3.7 + uses: actions/setup-python@v2 + with: + python-version: 3.7 + + - name: Install dependencies + run: | + sudo apt install -y libgmp3-dev + python -m pip install --upgrade pip + pip install starknet-devnet + - name: Compile Contracts run: yarn compile - name: Run unit tests - run: yarn test:hardhat - - - name: Setup Python - uses: actions/setup-python@v2 - with: - python-version: "3.7" - - - name: Install pytest - run: python -m pip install --upgrade pytest - - - name: Install cairo lang - run: python -m pip install --upgrade cairo-lang>=0.7.1 - - - name: Run python unit tests - run: yarn test:pytest + run: | + starknet-devnet -p 5000 & + sleep 10 && + curl http://localhost:5000 && + yarn test diff --git a/README.md b/README.md index e53b3685..e1f1151d 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,6 @@ Snapshot is open-sourced software licensed under the © [MIT license](LICENSE). Usage ----- -The Starknet Hardhat plugin uses a docker container so make sure you have a running Docker daemon. More information about the plugin can be found [here](https://github.com/Shard-Labs/starknet-hardhat-plugin) ### Install Python and Yarn requirements: @@ -46,6 +45,11 @@ yarn ```bash yarn compile ``` +### Install and start StarkNet Devnet (In separate terminal): +```bash +pip install starknet-devnet +starknet-devnet +``` ### Run tests: ```bash diff --git a/contracts/starknet/lib/types.cairo b/contracts/starknet/lib/types.cairo new file mode 100644 index 00000000..546f5dba --- /dev/null +++ b/contracts/starknet/lib/types.cairo @@ -0,0 +1,12 @@ +from starkware.cairo.common.math import assert_nn_le, assert_lt_felt + +struct EthAddress: + member value : felt +end + +# Checks that input is 160 bits long then if so, casts to the EthAddress type. +func to_ethereum_address{range_check_ptr}(input : felt) -> (address : EthAddress): + assert_lt_felt(input, 2 ** 160) + tempvar address : EthAddress = EthAddress(value=input) + return (address) +end diff --git a/contracts/starknet/README.md b/contracts/starknet/mocks/README.md similarity index 100% rename from contracts/starknet/README.md rename to contracts/starknet/mocks/README.md diff --git a/contracts/starknet/L1AuthMock.cairo b/contracts/starknet/mocks/l1_auth_mock.cairo similarity index 100% rename from contracts/starknet/L1AuthMock.cairo rename to contracts/starknet/mocks/l1_auth_mock.cairo diff --git a/contracts/starknet/L2AuthMock.cairo b/contracts/starknet/mocks/l2_auth_mock.cairo similarity index 100% rename from contracts/starknet/L2AuthMock.cairo rename to contracts/starknet/mocks/l2_auth_mock.cairo diff --git a/contracts/starknet/strategies/vanilla_voting_strategy.cairo b/contracts/starknet/strategies/vanilla_voting_strategy.cairo new file mode 100644 index 00000000..6a017a9c --- /dev/null +++ b/contracts/starknet/strategies/vanilla_voting_strategy.cairo @@ -0,0 +1,12 @@ +%lang starknet + +from starkware.cairo.common.uint256 import Uint256 +from contracts.starknet.lib.types import EthAddress + +# Returns a voting power of 1 for every address it is queried with. +@view +func get_voting_power{range_check_ptr}( + block : felt, address : EthAddress, params_len : felt, params : felt*) -> ( + voting_power : Uint256): + return (Uint256(1, 0)) +end diff --git a/hardhat.config.ts b/hardhat.config.ts index 2de91fb3..9c149432 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -52,7 +52,7 @@ const config: HardhatUserConfig = { url: 'http://localhost:8545', }, starknetDevnet: { - url: 'http://localhost:8000/', + url: 'http://localhost:5000/', }, }, gasReporter: { @@ -62,14 +62,10 @@ const config: HardhatUserConfig = { etherscan: { apiKey: process.env.ETHERSCAN_API_KEY, }, - // cairo: { - // venv: "active" - // venv: process.env.VIRTUAL_ENV - // }, - // paths: { - // sources: "contracts/ethereum", - // starknetSources: "contracts/starknet" - // } + starknet: { + venv: 'active', + network: 'starknetDevnet', + }, }; export default config; diff --git a/package.json b/package.json index 09b45751..8070b487 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,8 @@ "start:l1": "hardhat node", "format:l1": "prettier --write 'contracts/**/*.sol'", "check-format:l1": "prettier -c 'contracts/**/*.sol'", - "format:l2": "cairo-format -i contracts/**/*.cairo", - "check-format:l2": "cairo-format -c contracts/**/*.cairo", + "format:l2": "cairo-format -c contracts/starknet/**/*.cairo", + "check-format:l2": "cairo-format -c contracts/starknet/**/*.cairo", "format:ts": "eslint . --ext .ts --fix", "check-format:ts": "eslint . --ext .ts", "format": "yarn format:l1 && yarn format:l2 && yarn format:ts", @@ -32,7 +32,7 @@ "@nomiclabs/hardhat-etherscan": "^2.1.8", "@nomiclabs/hardhat-solhint": "^2.0.0", "@nomiclabs/hardhat-waffle": "^2.0.2", - "@shardlabs/starknet-hardhat-plugin": "^0.3.11", + "@shardlabs/starknet-hardhat-plugin": "^0.4.2", "@typechain/ethers-v5": "^7.2.0", "@typechain/hardhat": "^2.3.1", "@types/chai": "^4.3.0", diff --git a/test/starknet/shared/helpers.ts b/test/starknet/shared/helpers.ts new file mode 100644 index 00000000..cd0414dc --- /dev/null +++ b/test/starknet/shared/helpers.ts @@ -0,0 +1,19 @@ +export function assert(condition: boolean, message = 'Assertion Failed'): boolean { + if (!condition) { + throw message; + } + return condition; +} + +export function hexToBytes(hex: string): number[] { + const bytes = []; + for (let c = 2; c < hex.length; c += 2) bytes.push(parseInt(hex.substring(c, c + 2), 16)); + return bytes; +} + +export function bytesToHex(bytes: number[]): string { + const body = Array.from(bytes, function (byte) { + return ('0' + (byte & 0xff).toString(16)).slice(-2); + }).join(''); + return '0x' + body; +} diff --git a/test/starknet/shared/types.ts b/test/starknet/shared/types.ts new file mode 100644 index 00000000..b7183d81 --- /dev/null +++ b/test/starknet/shared/types.ts @@ -0,0 +1,73 @@ +import { assert, hexToBytes, bytesToHex } from './helpers'; + +//TODO: add toBytes for IntsSequence +export class IntsSequence { + values: bigint[]; + bytesLength: number; + + constructor(values: bigint[], bytesLength: number) { + this.values = values; + this.bytesLength = bytesLength; + } + + toSplitUint256(): SplitUint256 { + const rem = this.bytesLength % 8; + let uint = this.values[this.values.length - 1]; + let shift = BigInt(0); + if (rem == 0) { + shift += BigInt(64); + } else { + shift += BigInt(rem * 8); + } + for (let i = 0; i < this.values.length - 1; i++) { + uint += this.values[this.values.length - 2 - i] << BigInt(shift); + shift += BigInt(64); + } + return SplitUint256.fromUint(uint); + } + + static fromBytes(bytes: number[]): IntsSequence { + const ints_array: bigint[] = []; + for (let i = 0; i < bytes.length; i += 8) { + ints_array.push(BigInt(bytesToHex(bytes.slice(i + 0, i + 8)))); + } + return new IntsSequence(ints_array, bytes.length); + } + + static fromUint(uint: bigint): IntsSequence { + let hex = uint.toString(16); + if (hex.length % 2 != 0) { + hex = '0x0' + hex; + } else { + hex = '0x' + hex; + } + return IntsSequence.fromBytes(hexToBytes(hex)); + } +} + +export class SplitUint256 { + low: bigint; + high: bigint; + + constructor(low: bigint, high: bigint) { + this.low = low; + this.high = high; + } + + toUint(): bigint { + const uint = this.low + (this.high << BigInt(128)); + return uint; + } + + static fromUint(uint: bigint): SplitUint256 { + assert(uint < BigInt(1) << BigInt(256), 'Number too large'); + assert(BigInt(0) <= uint, 'Number cannot be negative'); + const low = uint & ((BigInt(1) << BigInt(128)) - BigInt(1)); + const high = uint >> BigInt(128); + return new SplitUint256(low, high); + } + + toHex(): string { + return '0x' + this.toUint().toString(16); + } +} diff --git a/test/starknet/vanilla_voting_strategy.ts b/test/starknet/vanilla_voting_strategy.ts new file mode 100644 index 00000000..91f62f2c --- /dev/null +++ b/test/starknet/vanilla_voting_strategy.ts @@ -0,0 +1,26 @@ +import { StarknetContract } from 'hardhat/types/runtime'; +import { expect } from 'chai'; +import { starknet } from 'hardhat'; +import { SplitUint256 } from './shared/types'; + +async function setup() { + const vanillaVotingStrategyFactory = await starknet.getContractFactory( + './contracts/starknet/strategies/vanilla_voting_strategy.cairo' + ); + const vanillaVotingStrategy = await vanillaVotingStrategyFactory.deploy(); + return { + vanillaVotingStrategy: vanillaVotingStrategy as StarknetContract, + }; +} + +describe('Snapshot X Vanilla Voting Strategy:', () => { + it('The voting strategy should return a voting power of 1', async () => { + const { vanillaVotingStrategy } = await setup(); + const { voting_power: vp } = await vanillaVotingStrategy.call('get_voting_power', { + block: 1, + address: { value: BigInt('0xffffffffffffffffffff') }, + params: [], + }); + expect(new SplitUint256(vp.low, vp.high)).to.deep.equal(SplitUint256.fromUint(BigInt(1))); + }).timeout(60000); +}); diff --git a/yarn.lock b/yarn.lock index 9f29e8ea..ea8f4de5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -996,15 +996,17 @@ __metadata: languageName: node linkType: hard -"@shardlabs/starknet-hardhat-plugin@npm:^0.3.11": - version: 0.3.11 - resolution: "@shardlabs/starknet-hardhat-plugin@npm:0.3.11" +"@shardlabs/starknet-hardhat-plugin@npm:^0.4.2": + version: 0.4.2 + resolution: "@shardlabs/starknet-hardhat-plugin@npm:0.4.2" dependencies: "@nomiclabs/hardhat-docker": ^2.0.2 axios: ^0.24.0 glob: ^7.2.0 + starknet: ^2.5.1 + peerDependencies: hardhat: ^2.8.2 - checksum: 012dcb003b24179eec98a12224ab61fdf6efa5c8aa3935908f24e3dc57c3c651d1daab09d52ce0aff7743f51b31ec359a93af95e6a090498c83ede6841b734a4 + checksum: 55c9b5823f498d39796a411dca3ede5754620b754a60da252301929bd96d7950980be78f31e43858d6ec48682b0e5ea04c09a217cbaa86ecc2a962236a49ed65 languageName: node linkType: hard @@ -6734,7 +6736,7 @@ fsevents@~2.3.2: languageName: node linkType: hard -"hardhat@npm:^2.8.2, hardhat@npm:^2.8.3": +"hardhat@npm:^2.8.3": version: 2.8.3 resolution: "hardhat@npm:2.8.3" dependencies: @@ -11491,7 +11493,7 @@ resolve@1.17.0: "@nomiclabs/hardhat-etherscan": ^2.1.8 "@nomiclabs/hardhat-solhint": ^2.0.0 "@nomiclabs/hardhat-waffle": ^2.0.2 - "@shardlabs/starknet-hardhat-plugin": ^0.3.11 + "@shardlabs/starknet-hardhat-plugin": ^0.4.2 "@typechain/ethers-v5": ^7.2.0 "@typechain/hardhat": ^2.3.1 "@types/chai": ^4.3.0