Compare commits

...

52 Commits

Author SHA1 Message Date
Bogdan-Ștefan Neacșu dd83631be1 Rename to avoid conflict 2022-09-20 18:01:12 +03:00
Bogdan-Ștefan Neacșu 405ca63c7b Mixnet execute msg 2022-09-20 17:41:26 +03:00
Bogdan-Ștefan Neacșu 7f17522666 Working send! 2022-09-20 17:41:25 +03:00
Bogdan-Ștefan Neacșu ba158f822d Have Clone and Debug for CosmosLedger 2022-09-20 17:40:23 +03:00
Bogdan-Ștefan Neacșu 0f147750ae Add sign function 2022-09-20 17:40:23 +03:00
Bogdan-Ștefan Neacșu 60d17389d0 Get address and add docs 2022-09-20 17:40:23 +03:00
Bogdan-Ștefan Neacșu 94d70e47fc Get version of ledger 2022-09-20 17:40:23 +03:00
Jon Häggblad ecbf5296a5 validator-api: guard against funny numbers in apy calc (#1636) 2022-09-19 14:37:09 +02:00
Jon Häggblad f3454409f8 Upgrade nym-connect to tauri 1.1.1 (#1635) 2022-09-19 11:34:33 +02:00
Gala 7d2e90b69f Merge pull request #1625 from nymtech/407-ne-filters-granularity
NE: 407 filters granularity
2022-09-19 11:28:26 +02:00
Gala bb2732bcc6 Merge pull request #1619 from nymtech/413-ne-content
NE: Display raw bond instead of self %
2022-09-19 11:16:16 +02:00
Gala d196993993 fix build 2022-09-19 11:08:16 +02:00
Jon Häggblad 065fe812ae Rename crate to nym-wallet-recovery-cli (#1633)
* Rename to nym-wallet-recovery-cli

* Rename crate name too

* Rename workspace member too

* Cargo.lock
2022-09-19 10:22:37 +02:00
Jon Häggblad 2aa18fb77c nym-wallet: add log window (#1618)
* Add a second entry point to the webpack config for the logging window

* tauri operations to show a log window

* LogViewer react component

* Upgrade tauri and use default tauri app menu for MacOS and add `Help` menu with `Show log` entry to show the logging window

* wip

* Proof of concept

* Fix format inside debug with ferm

* Put new menubar and log behind env variable flag

* Remove unused deps

* rustfmt

* Add changelog note

* Fix up imports

* Remove old code

* Improve log viewer

* Remove old code

* Add color to output, even if tauri hides it

* Remove redundant level from tauri log msg

* Since menu bar visible by default, change feature flag name

* Fix up webpack config so correct chunks get injected into entry points and remove inline CSS causing CSP issue

Co-authored-by: Mark Sinclair <mmsinclair@gmail.com>
2022-09-16 09:38:52 +01:00
Jon Häggblad 8a0e7fb9d6 validator-api: add missing clap argument (#1629) 2022-09-15 22:31:31 +02:00
Dave Hrycyszyn 13c2ca4a78 Noted a correct npm publish command 2022-09-15 16:28:56 +01:00
Dave Hrycyszyn 6af84535fa Formatting 2022-09-15 16:27:44 +01:00
Mark Sinclair c8699cbe8d wasm-client: fix up dependencies and feature flags so that wasm-pack build works (#1585)
* wasm-client: fix up dependencies and feature flags so that wasm-pack build works

* Update CHANGELOG
2022-09-15 16:11:49 +01:00
Drazen Urch 974163da97 Reduce iterations when delegator last_claimed_height is 0 (#1609)
* Reduce iterations when delegator last_claimed_height is 0

* Fix typo
2022-09-15 17:10:35 +02:00
Raphaël Walther 7290e479db Notification test 2022-09-15 17:01:49 +02:00
Raphaël Walther c7b728318c Notification test 2022-09-15 16:57:25 +02:00
Dave Hrycyszyn 5c6d31bcb5 README fix 2022-09-15 15:48:58 +01:00
Dave Hrycyszyn 3823292ba8 Bumping version number to 1.0.0 2022-09-15 15:48:42 +01:00
Bogdan-Ștefan Neacşu 3eeda4a421 Apply fix for socket_state too (#1627) 2022-09-15 17:35:41 +03:00
Gala d039c25b55 create a custom label for stake sat 2022-09-15 14:27:52 +02:00
Drazen Urch 16ef1c547b Remove eth feature, and BBBC related code (#1612)
* Remove eth feature, and BBBC related code

* Burn some more, especially clients
2022-09-15 13:58:44 +02:00
Dave Hrycyszyn e804b014a8 Cargo lock excluding webassembly client 2022-09-15 12:25:36 +01:00
Dave Hrycyszyn e406a05694 Re-excluding clients/webassembly which breaks cargo build --all 2022-09-15 12:24:04 +01:00
Gala 057b3456a7 more granulatity on filters 2022-09-15 12:49:27 +02:00
Dave Hrycyszyn 5ac124e159 Adding the webassembly client to the main build
This gives type hinting in editors. I can no longer remember why we
didn't set it up like this a long time ago, but it builds and tests
fine. Feel free to revert if it causes any problems.
2022-09-15 11:33:16 +01:00
Jon Häggblad f29c6a0550 Merge pull request #1622 from nymtech/jon/fix/shutdown-in-gateway-client
gateway-client: fix shutdown
2022-09-14 17:00:48 +02:00
Jon Häggblad 242a6d13af gateway-client: fix shutdown 2022-09-14 16:24:05 +02:00
Gala 7816b4c839 some refactor 2022-09-14 13:58:04 +02:00
Gala e7ed48e55e trying to fix the build 2022-09-14 13:35:22 +02:00
Gala 63855f6ca4 display pledge_amount instead of self on mix nodes page 2022-09-14 13:13:08 +02:00
Dave Hrycyszyn 2f53e40355 Switching to mainnet validator API 2022-09-14 11:49:06 +01:00
Dave Hrycyszyn bb9753cda6 Switching webassembly client to sync web assembly (async loading doens't work) 2022-09-14 11:49:06 +01:00
Dave Hrycyszyn 95d0afdeb6 Whitespace fix 2022-09-14 11:49:06 +01:00
mx fbe02fa7fb added new blockstream endpoint to example allowed.list (#1611)
* added new blockstream endpoint to example allowed.list

* updated changelog
2022-09-14 11:07:19 +01:00
dependabot[bot] 46e206e8f0 build(deps): bump terser from 4.8.0 to 4.8.1 (#1468)
Bumps [terser](https://github.com/terser/terser) from 4.8.0 to 4.8.1.
- [Release notes](https://github.com/terser/terser/releases)
- [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/terser/terser/commits)

---
updated-dependencies:
- dependency-name: terser
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-14 11:07:05 +01:00
dependabot[bot] 55bdcecffb Bump parse-url from 6.0.0 to 6.0.5 (#1497)
Bumps [parse-url](https://github.com/IonicaBizau/parse-url) from 6.0.0 to 6.0.5.
- [Release notes](https://github.com/IonicaBizau/parse-url/releases)
- [Commits](https://github.com/IonicaBizau/parse-url/compare/6.0.0...6.0.5)

---
updated-dependencies:
- dependency-name: parse-url
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-14 11:05:39 +01:00
dependabot[bot] 2ee4b8fec6 Bump @openzeppelin/contracts in /contracts/basic-bandwidth-generation (#1545)
Bumps [@openzeppelin/contracts](https://github.com/OpenZeppelin/openzeppelin-contracts) from 4.5.0 to 4.7.3.
- [Release notes](https://github.com/OpenZeppelin/openzeppelin-contracts/releases)
- [Changelog](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/CHANGELOG.md)
- [Commits](https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v4.5.0...v4.7.3)

---
updated-dependencies:
- dependency-name: "@openzeppelin/contracts"
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-14 11:05:07 +01:00
dependabot[bot] 67900956f8 Bump terser in /clients/native/examples/js-examples/websocket (#1620)
Bumps [terser](https://github.com/terser/terser) from 5.12.1 to 5.15.0.
- [Release notes](https://github.com/terser/terser/releases)
- [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/terser/terser/compare/v5.12.1...v5.15.0)

---
updated-dependencies:
- dependency-name: terser
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-14 11:04:14 +01:00
dependabot[bot] 29166c1d6a Bump terser from 5.10.0 to 5.15.0 in /testnet-faucet (#1621)
Bumps [terser](https://github.com/terser/terser) from 5.10.0 to 5.15.0.
- [Release notes](https://github.com/terser/terser/releases)
- [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/terser/terser/compare/v5.10.0...v5.15.0)

---
updated-dependencies:
- dependency-name: terser
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-14 11:03:56 +01:00
Dave Hrycyszyn db24170752 Fixing npm audit in js example code 2022-09-14 10:32:39 +01:00
Jon Häggblad fc90d5a389 ci: more cargo clean for windows
Not ideal, we need runners with more disk space
2022-09-14 08:31:05 +02:00
Raphaël Walther af1a83fe83 Test for notification 2022-09-13 19:35:22 +02:00
Raphaël Walther e12b69e58f Test for notification 2022-09-13 19:28:46 +02:00
Raphaël Walther d38614b15c Test for notification 2022-09-13 19:22:09 +02:00
Raphaël Walther 627d12239e Added keybase team variable (#1537)
* Added keybase team variable

* Amended notification
2022-09-13 18:13:18 +01:00
Gala 1af1370f23 display raw bond instead of self % 2022-09-13 17:00:37 +02:00
Mark Sinclair 124103d51b Mixnet and vesting contracts v1.0.2 (#1616) 2022-09-13 09:47:18 +01:00
173 changed files with 3852 additions and 7448 deletions
+6
View File
@@ -107,6 +107,12 @@ jobs:
command: build
args: --workspace --features=coconut
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- name: Run all tests with coconut enabled
uses: actions-rs/cargo@v1
with:
@@ -166,8 +166,8 @@ jobs:
GIT_BRANCH: "${GITHUB_REF##*/}"
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
KEYBASE_NYM_CHANNEL: "ci-nightly"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMTECH_TEAM }}"
KEYBASE_NYM_CHANNEL: "${{ secrets.KEYBASE_CHANNEL_DEV_CORE_ID }}"
IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}"
uses: docker://keybaseio/client:stable-node
with:
@@ -1,26 +0,0 @@
name: Nightly builds on dispatch
on: workflow_dispatch
jobs:
notification:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v2
- name: Keybase - Node Install
run: npm install
working-directory: .github/workflows/support-files
- name: Keybase - Send Notification
env:
NYM_NOTIFICATION_KIND: nightly
NYM_PROJECT_NAME: "Notification test"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH: "${GITHUB_REF##*/}"
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
KEYBASE_NYM_CHANNEL: "ci-nightly"
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
@@ -89,7 +89,7 @@ async function sendKeybaseMessage(messageBody) {
});
const channel = {
name: 'nymtech_bot',
name: context.env.KEYBASE_NYMBOT_TEAM || 'nymtech_bot',
membersType: 'team',
topicName: context.keybase.channel,
topic_type: 'CHAT',
+3
View File
@@ -8,15 +8,18 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- nym-cli: added CLI tool for interacting with the Nyx blockchain and Nym mixnet smart contracts ([#1577])
- validator-client: added `query_contract_smart` and `query_contract_raw` on `NymdClient` ([#1558])
- network-requester: added additional Blockstream Green wallet endpoint to `example.allowed.list` ([#1611](https://github.com/nymtech/nym/pull/1611))
### Changed
- validator-client: made `fee` argument optional for `execute` and `execute_multiple` ([#1541])
- socks5 client: graceful shutdown should fix error on disconnect in nym-connect ([#1591])
- wasm-client: fixed build errors on MacOS and changed example JS code to use mainnet ([#1585])
[#1541]: https://github.com/nymtech/nym/pull/1541
[#1558]: https://github.com/nymtech/nym/pull/1558
[#1577]: https://github.com/nymtech/nym/pull/1577
[#1585]: https://github.com/nymtech/nym/pull/1585
[#1591]: https://github.com/nymtech/nym/pull/1591
Generated
+269 -226
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -40,6 +40,7 @@ members = [
"common/crypto/dkg",
"common/execute",
"common/inclusion-probability",
"common/ledger",
"common/mixnode-common",
"common/network-defaults",
"common/nonexhaustive-delayqueue",
@@ -85,4 +86,4 @@ default-members = [
"explorer-api",
]
exclude = ["explorer", "contracts", "tokenomics-py", "clients/webassembly", "nym-wallet", "nym-connect"]
exclude = ["explorer", "contracts", "clients/webassembly", "nym-wallet", "nym-connect"]
+24 -7
View File
@@ -2,10 +2,10 @@ test: clippy-all cargo-test wasm fmt
test-all: test cargo-test-expensive
no-clippy: build cargo-test wasm fmt
happy: fmt clippy-happy test
clippy-all: clippy-all-main clippy-all-contracts clippy-all-wallet clippy-all-connect
clippy-all: clippy-main clippy-coconut clippy-all-contracts clippy-all-wallet clippy-all-connect
clippy-happy: clippy-happy-main clippy-happy-contracts clippy-happy-wallet clippy-happy-connect
cargo-test: test-main test-contracts test-wallet test-connect
cargo-test-expensive: test-main-expensive test-contracts-expensive test-wallet-expensive test-connect-expensive
cargo-test: test-main test-contracts test-wallet test-connect test-coconut
cargo-test-expensive: test-main-expensive test-contracts-expensive test-wallet-expensive test-connect-expensive test-coconut-expensive
build: build-contracts build-wallet build-main build-connect
fmt: fmt-main fmt-contracts fmt-wallet fmt-connect
@@ -21,8 +21,15 @@ clippy-happy-wallet:
clippy-happy-connect:
cargo clippy --manifest-path nym-connect/Cargo.toml
clippy-all-main:
cargo clippy --workspace --all-features -- -D warnings
clippy-main:
cargo clippy --workspace -- -D warnings
clippy-coconut:
cargo clippy --workspace --features coconut -- -D warnings
clippy-wasm:
cargo clippy --workspace --features wasm -- -D warnings
clippy-all-contracts:
cargo clippy --workspace --manifest-path contracts/Cargo.toml --all-features --target wasm32-unknown-unknown -- -D warnings
@@ -34,10 +41,20 @@ clippy-all-connect:
cargo clippy --workspace --manifest-path nym-connect/Cargo.toml --all-features -- -D warnings
test-main:
cargo test --all-features --workspace
cargo test --workspace
test-coconut:
cargo test --workspace --features coconut
test-wasm:
cargo test --workspace --features wasm
test-main-expensive:
cargo test --all-features --workspace -- --ignored
cargo test --workspace -- --ignored
test-coconut-expensive:
cargo test --workspace --features coconut -- --ignored
test-contracts:
cargo test --manifest-path contracts/Cargo.toml --all-features
-32
View File
@@ -121,16 +121,6 @@ impl<T: NymConfig> Config<T> {
self.client.gateway_endpoint.gateway_id = id.into();
}
#[cfg(not(feature = "coconut"))]
pub fn with_eth_private_key<S: Into<String>>(&mut self, eth_private_key: S) {
self.client.eth_private_key = eth_private_key.into();
}
#[cfg(not(feature = "coconut"))]
pub fn with_eth_endpoint<S: Into<String>>(&mut self, eth_endpoint: S) {
self.client.eth_endpoint = eth_endpoint.into();
}
pub fn set_custom_validator_apis(&mut self, validator_api_urls: Vec<Url>) {
self.client.validator_api_urls = validator_api_urls;
}
@@ -209,16 +199,6 @@ impl<T: NymConfig> Config<T> {
self.client.database_path.clone()
}
#[cfg(not(feature = "coconut"))]
pub fn get_eth_endpoint(&self) -> String {
self.client.eth_endpoint.clone()
}
#[cfg(not(feature = "coconut"))]
pub fn get_eth_private_key(&self) -> String {
self.client.eth_private_key.clone()
}
// Debug getters
pub fn get_average_packet_delay(&self) -> Duration {
self.debug.average_packet_delay
@@ -342,14 +322,6 @@ pub struct Client<T> {
/// Path to the database containing bandwidth credentials of this client.
database_path: PathBuf,
/// Ethereum private key.
#[cfg(not(feature = "coconut"))]
eth_private_key: String,
/// Address to an Ethereum full node.
#[cfg(not(feature = "coconut"))]
eth_endpoint: String,
/// nym_home_directory specifies absolute path to the home nym Clients directory.
/// It is expected to use default value and hence .toml file should not redefine this field.
nym_root_directory: PathBuf,
@@ -375,10 +347,6 @@ impl<T: NymConfig> Default for Client<T> {
reply_encryption_key_store_path: Default::default(),
gateway_endpoint: Default::default(),
database_path: Default::default(),
#[cfg(not(feature = "coconut"))]
eth_private_key: "".to_string(),
#[cfg(not(feature = "coconut"))]
eth_endpoint: "".to_string(),
nym_root_directory: T::default_root_directory(),
super_struct: Default::default(),
}
-1
View File
@@ -50,7 +50,6 @@ websocket-requests = { path = "websocket-requests" }
[features]
coconut = ["coconut-interface", "credentials", "credentials/coconut", "gateway-requests/coconut", "gateway-client/coconut", "client-core/coconut"]
eth = []
[dev-dependencies]
serde_json = "1.0" # for the "textsend" example
+103 -21
View File
@@ -27,6 +27,58 @@
"node": ">=10.0.0"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
"integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
"dependencies": {
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.9"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/set-array": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/source-map": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
"integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.15",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz",
"integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==",
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -3529,13 +3581,13 @@
}
},
"node_modules/terser": {
"version": "5.12.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.12.1.tgz",
"integrity": "sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ==",
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz",
"integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==",
"dependencies": {
"@jridgewell/source-map": "^0.3.2",
"acorn": "^8.5.0",
"commander": "^2.20.0",
"source-map": "~0.7.2",
"source-map-support": "~0.5.20"
},
"bin": {
@@ -3583,14 +3635,6 @@
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
},
"node_modules/terser/node_modules/source-map": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
"engines": {
"node": ">= 8"
}
},
"node_modules/thunky": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
@@ -4330,6 +4374,49 @@
"integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==",
"dev": true
},
"@jridgewell/gen-mapping": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
"integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
"requires": {
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"@jridgewell/resolve-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w=="
},
"@jridgewell/set-array": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw=="
},
"@jridgewell/source-map": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
"integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
"requires": {
"@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"@jridgewell/sourcemap-codec": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
},
"@jridgewell/trace-mapping": {
"version": "0.3.15",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz",
"integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==",
"requires": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -7058,13 +7145,13 @@
"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ=="
},
"terser": {
"version": "5.12.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.12.1.tgz",
"integrity": "sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ==",
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz",
"integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==",
"requires": {
"@jridgewell/source-map": "^0.3.2",
"acorn": "^8.5.0",
"commander": "^2.20.0",
"source-map": "~0.7.2",
"source-map-support": "~0.5.20"
},
"dependencies": {
@@ -7072,11 +7159,6 @@
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
},
"source-map": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ=="
}
}
},
@@ -49,12 +49,6 @@ reply_encryption_key_store_path = '{{ client.reply_encryption_key_store_path }}'
# Path to the database containing bandwidth credentials
database_path = '{{ client.database_path }}'
# Ethereum private key.
eth_private_key = '{{ client.eth_private_key }}'
# Addess to an Ethereum full node.
eth_endpoint = '{{ client.eth_endpoint }}'
##### additional client config options #####
# A gateway specific, optional, base58 stringified shared key used for
-2
View File
@@ -190,8 +190,6 @@ impl NymClient {
let bandwidth_controller = BandwidthController::new(
credential_storage::initialise_storage(self.config.get_base().get_database_path())
.await,
self.config.get_base().get_eth_endpoint(),
self.config.get_base().get_eth_private_key(),
)
.expect("Could not create bandwidth controller");
+2 -29
View File
@@ -10,9 +10,6 @@ use crate::{
commands::{override_config, OverrideConfig},
};
#[cfg(all(feature = "eth", not(feature = "coconut")))]
use crate::commands::{DEFAULT_ETH_ENDPOINT, DEFAULT_ETH_PRIVATE_KEY};
#[derive(Args, Clone)]
pub(crate) struct Init {
/// Id of the nym-mixnet-client we want to create config for.
@@ -47,27 +44,9 @@ pub(crate) struct Init {
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement.
#[cfg(any(feature = "eth", feature = "coconut"))]
#[cfg(feature = "coconut")]
#[clap(long)]
enabled_credentials_mode: bool,
/// URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20
/// tokens. If you don't want to set this value, use --enabled-credentials-mode instead
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(
long,
default_value_if("enabled-credentials-mode", None, Some(DEFAULT_ETH_ENDPOINT))
)]
eth_endpoint: String,
/// Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't
/// want to set this value, use --enabled-credentials-mode instead")
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(
long,
default_value_if("enabled-credentials-mode", None, Some(DEFAULT_ETH_PRIVATE_KEY))
)]
eth_private_key: String,
}
impl From<Init> for OverrideConfig {
@@ -78,14 +57,8 @@ impl From<Init> for OverrideConfig {
port: init_config.port,
fastmode: init_config.fastmode,
#[cfg(any(feature = "eth", feature = "coconut"))]
#[cfg(feature = "coconut")]
enabled_credentials_mode: init_config.enabled_credentials_mode,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_private_key: Some(init_config.eth_private_key),
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_endpoint: Some(init_config.eth_endpoint),
}
}
}
+2 -34
View File
@@ -4,13 +4,6 @@
use crate::client::config::{Config, SocketType};
use clap::{Parser, Subcommand};
#[cfg(not(feature = "coconut"))]
pub(crate) const DEFAULT_ETH_ENDPOINT: &str =
"https://rinkeby.infura.io/v3/00000000000000000000000000000000";
#[cfg(not(feature = "coconut"))]
pub(crate) const DEFAULT_ETH_PRIVATE_KEY: &str =
"0000000000000000000000000000000000000000000000000000000000000001";
pub(crate) mod init;
pub(crate) mod run;
pub(crate) mod upgrade;
@@ -78,14 +71,8 @@ pub(crate) struct OverrideConfig {
port: Option<u16>,
fastmode: bool,
#[cfg(any(feature = "eth", feature = "coconut"))]
#[cfg(feature = "coconut")]
enabled_credentials_mode: bool,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_private_key: Option<String>,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_endpoint: Option<String>,
}
pub(crate) async fn execute(args: &Cli) {
@@ -117,32 +104,13 @@ pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Confi
config = config.with_port(port);
}
#[cfg(all(not(feature = "eth"), not(feature = "coconut")))]
{
config
.get_base_mut()
.with_eth_endpoint(DEFAULT_ETH_ENDPOINT.to_string());
config
.get_base_mut()
.with_eth_private_key(DEFAULT_ETH_PRIVATE_KEY.to_string());
}
#[cfg(any(feature = "eth", feature = "coconut"))]
#[cfg(feature = "coconut")]
{
if args.enabled_credentials_mode {
config.get_base_mut().with_disabled_credentials(false)
}
}
#[cfg(all(feature = "eth", not(feature = "coconut")))]
{
if let Some(eth_endpoint) = args.eth_endpoint {
config.get_base_mut().with_eth_endpoint(eth_endpoint);
}
if let Some(eth_private_key) = args.eth_private_key {
config.get_base_mut().with_eth_private_key(eth_private_key);
}
}
if args.fastmode {
config.get_base_mut().set_high_default_traffic_volume();
}
+2 -21
View File
@@ -36,21 +36,9 @@ pub(crate) struct Run {
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement.
#[cfg(any(feature = "eth", feature = "coconut"))]
#[cfg(feature = "coconut")]
#[clap(long)]
enabled_credentials_mode: bool,
/// URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20
/// tokens. If you don't want to set this value, use --enabled-credentials-mode instead
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(long)]
eth_endpoint: Option<String>,
/// Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't
/// want to set this value, use --enabled-credentials-mode instead")
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(long)]
eth_private_key: Option<String>,
}
impl From<Run> for OverrideConfig {
@@ -60,15 +48,8 @@ impl From<Run> for OverrideConfig {
disable_socket: run_config.disable_socket,
port: run_config.port,
fastmode: false,
#[cfg(any(feature = "eth", feature = "coconut"))]
#[cfg(feature = "coconut")]
enabled_credentials_mode: run_config.enabled_credentials_mode,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_private_key: run_config.eth_private_key,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_endpoint: run_config.eth_endpoint,
}
}
}
@@ -49,12 +49,6 @@ reply_encryption_key_store_path = '{{ client.reply_encryption_key_store_path }}'
# Path to the database containing bandwidth credentials
database_path = '{{ client.database_path }}'
# Ethereum private key.
eth_private_key = '{{ client.eth_private_key }}'
# Addess to an Ethereum full node.
eth_endpoint = '{{ client.eth_endpoint }}'
##### additional client config options #####
# A gateway specific, optional, base58 stringified shared key used for
-2
View File
@@ -191,8 +191,6 @@ impl NymClient {
let bandwidth_controller = BandwidthController::new(
credential_storage::initialise_storage(self.config.get_base().get_database_path())
.await,
self.config.get_base().get_eth_endpoint(),
self.config.get_base().get_eth_private_key(),
)
.expect("Could not create bandwidth controller");
+2 -30
View File
@@ -10,9 +10,6 @@ use crate::{
commands::{override_config, OverrideConfig},
};
#[cfg(all(feature = "eth", not(feature = "coconut")))]
use crate::commands::{DEFAULT_ETH_ENDPOINT, DEFAULT_ETH_PRIVATE_KEY};
#[derive(Args, Clone)]
pub(crate) struct Init {
/// Id of the nym-mixnet-client we want to create config for.
@@ -47,27 +44,9 @@ pub(crate) struct Init {
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement.
#[cfg(any(feature = "eth", feature = "coconut"))]
#[cfg(feature = "coconut")]
#[clap(long)]
enabled_credentials_mode: bool,
/// URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20
/// tokens. If you don't want to set this value, use --enabled-credentials-mode instead
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(
long,
default_value_if("enabled-credentials-mode", None, Some(DEFAULT_ETH_ENDPOINT))
)]
eth_endpoint: String,
/// Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't
/// want to set this value, use --enabled-credentials-mode instead")
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(
long,
default_value_if("enabled-credentials-mode", None, Some(DEFAULT_ETH_PRIVATE_KEY))
)]
eth_private_key: String,
}
impl From<Init> for OverrideConfig {
@@ -76,15 +55,8 @@ impl From<Init> for OverrideConfig {
validators: init_config.validators,
port: init_config.port,
fastmode: init_config.fastmode,
#[cfg(any(feature = "eth", feature = "coconut"))]
#[cfg(feature = "coconut")]
enabled_credentials_mode: init_config.enabled_credentials_mode,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_private_key: Some(init_config.eth_private_key),
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_endpoint: Some(init_config.eth_endpoint),
}
}
}
+2 -34
View File
@@ -9,13 +9,6 @@ pub mod init;
pub(crate) mod run;
pub(crate) mod upgrade;
#[cfg(not(feature = "coconut"))]
pub(crate) const DEFAULT_ETH_ENDPOINT: &str =
"https://rinkeby.infura.io/v3/00000000000000000000000000000000";
#[cfg(not(feature = "coconut"))]
pub(crate) const DEFAULT_ETH_PRIVATE_KEY: &str =
"0000000000000000000000000000000000000000000000000000000000000001";
fn long_version() -> String {
format!(
r#"
@@ -78,14 +71,8 @@ pub(crate) struct OverrideConfig {
port: Option<u16>,
fastmode: bool,
#[cfg(any(feature = "eth", feature = "coconut"))]
#[cfg(feature = "coconut")]
enabled_credentials_mode: bool,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_private_key: Option<String>,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_endpoint: Option<String>,
}
pub(crate) async fn execute(args: &Cli) {
@@ -111,31 +98,12 @@ pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Confi
config = config.with_port(port);
}
#[cfg(all(not(feature = "eth"), not(feature = "coconut")))]
{
config
.get_base_mut()
.with_eth_endpoint(DEFAULT_ETH_ENDPOINT.to_string());
config
.get_base_mut()
.with_eth_private_key(DEFAULT_ETH_PRIVATE_KEY.to_string());
}
#[cfg(any(feature = "eth", feature = "coconut"))]
#[cfg(feature = "coconut")]
{
if args.enabled_credentials_mode {
config.get_base_mut().with_disabled_credentials(false)
}
}
#[cfg(all(feature = "eth", not(feature = "coconut")))]
{
if let Some(eth_endpoint) = args.eth_endpoint {
config.get_base_mut().with_eth_endpoint(eth_endpoint);
}
if let Some(eth_private_key) = args.eth_private_key {
config.get_base_mut().with_eth_private_key(eth_private_key);
}
}
if args.fastmode {
config.get_base_mut().set_high_default_traffic_volume();
+2 -20
View File
@@ -40,21 +40,9 @@ pub(crate) struct Run {
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement.
#[cfg(any(feature = "eth", feature = "coconut"))]
#[cfg(feature = "coconut")]
#[clap(long)]
enabled_credentials_mode: bool,
/// URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20
/// tokens. If you don't want to set this value, use --enabled-credentials-mode instead
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(long)]
eth_endpoint: Option<String>,
/// Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't
/// want to set this value, use --enabled-credentials-mode instead
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(long)]
eth_private_key: Option<String>,
}
impl From<Run> for OverrideConfig {
@@ -64,14 +52,8 @@ impl From<Run> for OverrideConfig {
port: run_config.port,
fastmode: false,
#[cfg(any(feature = "eth", feature = "coconut"))]
#[cfg(feature = "coconut")]
enabled_credentials_mode: run_config.enabled_credentials_mode,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_private_key: run_config.eth_private_key,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_endpoint: run_config.eth_endpoint,
}
}
}
+2 -2
View File
@@ -1,7 +1,7 @@
[package]
name = "nym-client-wasm"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jedrzej Stuczynski <andrew@nymtech.net>"]
version = "1.0.1"
version = "1.0.0"
edition = "2021"
keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"]
license = "Apache-2.0"
@@ -32,7 +32,7 @@ credentials = { path = "../../common/credentials", optional = true }
crypto = { path = "../../common/crypto" }
nymsphinx = { path = "../../common/nymsphinx" }
topology = { path = "../../common/topology" }
gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm"] }
gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm", "coconut"] }
validator-client = { path = "../../common/client-libs/validator-client", default-features = false }
wasm-utils = { path = "../../common/wasm-utils" }
+4 -3
View File
@@ -11,6 +11,7 @@ This client is part of the [Nym](https://nymtech.net/docs) project. It's written
## Security Status
From a security point of view, this module is not yet complete. Key missing features include, but are not limited to: cover traffic, sending packets with delay according to Poisson distribution.
They should be implemented soon. You can build your applications, but don't rely on it for strong anonymity yet if your application needs cover traffic.
## Using it
@@ -37,6 +38,6 @@ To be clear, this is not something that most JS developers need to worry about,
If you're a Nym platform developer who's made changes to the Rust (or JS) files and wants to re-publish the package to NPM, here's how you do it:
1. `wasm-pack build --scope nymproject` builds the wasm binaries into the `pkg` directory (not in source control)
2. bump version numbers as necessary for SemVer
3. `wasm-pack publish --access=public` will publish your changed package to NPM
1. bump version numbers as necessary for SemVer
2. `wasm-pack build --scope nymproject` builds the wasm binaries into the `pkg` directory (not in source control)
3. `cd pkg && npm publish --access=public` will publish your changed package to NPM
+3 -3
View File
@@ -13,8 +13,8 @@
// limitations under the License.
import {
NymClient,
set_panic_hook
NymClient,
set_panic_hook
} from "@nymproject/nym-client-wasm"
// current limitation of rust-wasm for async stuff : (
@@ -25,7 +25,7 @@ async function main() {
set_panic_hook();
// validator server we will use to get topology from
const validator = "https://sandbox-validator.nymtech.net/api"; //"http://localhost:8081";
const validator = "https://validator.nymtech.net/api"; //"http://localhost:8081";
client = new NymClient(validator);
+115 -25
View File
@@ -36,6 +36,64 @@
"node": ">=10.0.0"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
"integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
"dev": true,
"dependencies": {
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.9"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/set-array": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/source-map": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
"integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
"dev": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
"dev": true
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.15",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz",
"integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==",
"dev": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -3272,14 +3330,14 @@
}
},
"node_modules/terser": {
"version": "5.12.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.12.1.tgz",
"integrity": "sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ==",
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz",
"integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==",
"dev": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.2",
"acorn": "^8.5.0",
"commander": "^2.20.0",
"source-map": "~0.7.2",
"source-map-support": "~0.5.20"
},
"bin": {
@@ -3372,15 +3430,6 @@
"url": "https://opencollective.com/webpack"
}
},
"node_modules/terser/node_modules/source-map": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/thunky": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
@@ -3828,6 +3877,55 @@
"integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==",
"dev": true
},
"@jridgewell/gen-mapping": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
"integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
"dev": true,
"requires": {
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"@jridgewell/resolve-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
"dev": true
},
"@jridgewell/set-array": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
"dev": true
},
"@jridgewell/source-map": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
"integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
"dev": true,
"requires": {
"@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"@jridgewell/sourcemap-codec": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
"dev": true
},
"@jridgewell/trace-mapping": {
"version": "0.3.15",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz",
"integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==",
"dev": true,
"requires": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -6334,23 +6432,15 @@
"dev": true
},
"terser": {
"version": "5.12.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.12.1.tgz",
"integrity": "sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ==",
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz",
"integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==",
"dev": true,
"requires": {
"@jridgewell/source-map": "^0.3.2",
"acorn": "^8.5.0",
"commander": "^2.20.0",
"source-map": "~0.7.2",
"source-map-support": "~0.5.20"
},
"dependencies": {
"source-map": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
"dev": true
}
}
},
"terser-webpack-plugin": {
@@ -9,7 +9,7 @@ module.exports = {
},
mode: "development",
plugins: [
new CopyWebpackPlugin({patterns: ['index.html']})
new CopyWebpackPlugin({ patterns: ['index.html'] })
],
experiments: { asyncWebAssembly: true }
experiments: { syncWebAssembly: true }
};
+4 -5
View File
@@ -10,13 +10,12 @@ edition = "2021"
# TODO: (for this and other crates), similarly to 'tokio', import only required "futures" modules rather than
# the entire crate
futures = "0.3"
json = "0.12.4"
log = "0.4"
thiserror = "1.0"
url = "2.2"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
secp256k1 = "0.20.3"
web3 = { version = "0.17.0", default-features = false }
secp256k1 = { version = "0.20.3", optional = true }
web3 = { version = "0.17.0", default-features = false, optional = true }
async-trait = { version = "0.1.51" }
# internal
@@ -80,5 +79,5 @@ features = ["js"]
[features]
coconut = ["gateway-requests/coconut", "coconut-interface", "validator-client", "credentials/coconut"]
wasm = ["web3/wasm", "web3/http", "web3/signing"]
default = ["web3/default"]
wasm = []
default = ["web3/default", "secp256k1"]
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
use crate::error::GatewayClientError;
use std::str::FromStr;
#[cfg(target_arch = "wasm32")]
use crate::wasm_storage::Storage;
@@ -15,6 +14,8 @@ use crate::wasm_storage::StorageError;
#[cfg(all(not(target_arch = "wasm32"), feature = "coconut"))]
use credential_storage::error::StorageError;
#[cfg(feature = "coconut")]
use std::str::FromStr;
#[cfg(feature = "coconut")]
use {
coconut_interface::Base58,
@@ -23,65 +24,12 @@ use {
},
};
#[cfg(not(feature = "coconut"))]
use {
credentials::token::bandwidth::TokenCredential,
crypto::asymmetric::identity,
network_defaults::{
eth_contract::ETH_ERC20_JSON_ABI, eth_contract::ETH_JSON_ABI, BANDWIDTH_VALUE,
ETH_BURN_FUNCTION_NAME, ETH_CONTRACT_ADDRESS, ETH_ERC20_APPROVE_FUNCTION_NAME,
ETH_ERC20_CONTRACT_ADDRESS, ETH_MIN_BLOCK_DEPTH, TOKENS_TO_BURN, UTOKENS_TO_BURN,
},
pemstore::traits::PemStorableKeyPair,
rand::rngs::OsRng,
secp256k1::SecretKey,
web3::{
contract::{Contract, Options},
ethabi::Token,
signing::{Key, SecretKeyRef},
transports::Http,
types::{Address, U256, U64},
Web3,
},
};
#[cfg(not(feature = "coconut"))]
pub fn eth_contract(web3: Web3<Http>) -> Contract<Http> {
Contract::from_json(
web3.eth(),
Address::from(ETH_CONTRACT_ADDRESS),
json::parse(ETH_JSON_ABI)
.expect("Invalid json abi")
.dump()
.as_bytes(),
)
.expect("Invalid json abi")
}
#[cfg(not(feature = "coconut"))]
pub fn eth_erc20_contract(web3: Web3<Http>) -> Contract<Http> {
Contract::from_json(
web3.eth(),
Address::from(ETH_ERC20_CONTRACT_ADDRESS),
json::parse(ETH_ERC20_JSON_ABI)
.expect("Invalid json abi")
.dump()
.as_bytes(),
)
.expect("Invalid json abi")
}
#[derive(Clone)]
pub struct BandwidthController<St: Storage> {
#[allow(dead_code)]
storage: St,
#[cfg(feature = "coconut")]
validator_endpoints: Vec<url::Url>,
#[cfg(not(feature = "coconut"))]
contract: Contract<Http>,
#[cfg(not(feature = "coconut"))]
erc20_contract: Contract<Http>,
#[cfg(not(feature = "coconut"))]
eth_private_key: SecretKey,
}
impl<St> BandwidthController<St>
@@ -97,60 +45,8 @@ where
}
#[cfg(not(feature = "coconut"))]
pub fn new(
storage: St,
eth_endpoint: String,
eth_private_key: String,
) -> Result<Self, GatewayClientError> {
// Fail early, on invalid url
let transport =
Http::new(&eth_endpoint).map_err(|_| GatewayClientError::InvalidURL(eth_endpoint))?;
let web3 = web3::Web3::new(transport);
// Fail early, on invalid abi
let contract = eth_contract(web3.clone());
let erc20_contract = eth_erc20_contract(web3);
let eth_private_key = secp256k1::SecretKey::from_str(&eth_private_key)
.map_err(|_| GatewayClientError::InvalidEthereumPrivateKey)?;
Ok(BandwidthController {
storage,
contract,
erc20_contract,
eth_private_key,
})
}
#[cfg(not(feature = "coconut"))]
async fn backup_keypair(&self, keypair: &identity::KeyPair) -> Result<(), GatewayClientError> {
self.storage
.insert_erc20_credential(
keypair.public_key().to_base58_string(),
keypair.private_key().to_base58_string(),
)
.await?;
Ok(())
}
#[cfg(not(feature = "coconut"))]
async fn restore_keypair(&self) -> Result<identity::KeyPair, GatewayClientError> {
let data = self.storage.get_next_erc20_credential().await?;
let public_key = identity::PublicKey::from_base58_string(data.public_key).unwrap();
let private_key = identity::PrivateKey::from_base58_string(data.private_key).unwrap();
Ok(identity::KeyPair::from_keys(private_key, public_key))
}
#[cfg(not(feature = "coconut"))]
async fn mark_keypair_as_spent(
&self,
keypair: &identity::KeyPair,
) -> Result<(), GatewayClientError> {
self.storage
.consume_erc20_credential(keypair.public_key().to_base58_string())
.await?;
Ok(())
pub fn new(storage: St) -> Result<Self, GatewayClientError> {
Ok(BandwidthController { storage })
}
#[cfg(feature = "coconut")]
@@ -179,206 +75,4 @@ where
&verification_key,
)?)
}
#[cfg(not(feature = "coconut"))]
pub async fn prepare_token_credential(
&self,
gateway_identity: identity::PublicKey,
gateway_owner: String,
) -> Result<TokenCredential, GatewayClientError> {
let kp = match self.restore_keypair().await {
Ok(kp) => kp,
Err(_) => {
let mut rng = OsRng;
let kp = identity::KeyPair::new(&mut rng);
self.backup_keypair(&kp).await?;
kp
}
};
let verification_key = *kp.public_key();
let signed_verification_key = kp.private_key().sign(&verification_key.to_bytes());
self.buy_token_credential(verification_key, signed_verification_key, gateway_owner)
.await?;
self.mark_keypair_as_spent(&kp).await?;
let message: Vec<u8> = verification_key
.to_bytes()
.iter()
.chain(gateway_identity.to_bytes().iter())
.copied()
.collect();
let signature = kp.private_key().sign(&message);
Ok(TokenCredential::new(
verification_key,
gateway_identity,
BANDWIDTH_VALUE,
signature,
))
}
#[cfg(not(feature = "coconut"))]
pub async fn buy_token_credential(
&self,
verification_key: identity::PublicKey,
signed_verification_key: identity::Signature,
gateway_owner: String,
) -> Result<(), GatewayClientError> {
let confirmations = if cfg!(debug_assertions) {
1
} else {
ETH_MIN_BLOCK_DEPTH
};
// 15 seconds per confirmation block + 10 seconds of network overhead + 20 seconds of wait for kill
log::info!(
"Waiting for Ethereum transaction. This should take about {} seconds",
(confirmations + 1) * 15 + 30
);
let mut options = Options::default();
let estimation = self
.erc20_contract
.estimate_gas(
ETH_ERC20_APPROVE_FUNCTION_NAME,
(
Token::Address(Address::from(ETH_CONTRACT_ADDRESS)),
Token::Uint(U256::from(UTOKENS_TO_BURN)),
),
SecretKeyRef::from(&self.eth_private_key).address(),
options.clone(),
)
.await?;
options.gas = Some(estimation);
log::info!("Calling ERC20 approve in 10 seconds with an estimated gas of {}. Kill the process if you want to abort", estimation);
#[cfg(not(target_arch = "wasm32"))]
tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
#[cfg(target_arch = "wasm32")]
if let Err(err) = fluvio_wasm_timer::Delay::new(std::time::Duration::from_secs(10)).await {
log::error!(
"the timer has gone away while waiting for possible kill! - {}",
err
);
}
let recipt = self
.erc20_contract
.signed_call_with_confirmations(
ETH_ERC20_APPROVE_FUNCTION_NAME,
(
Token::Address(Address::from(ETH_CONTRACT_ADDRESS)),
Token::Uint(U256::from(UTOKENS_TO_BURN)),
),
options,
1, // One confirmation is enough, as we'll be consuming the approved token next anyway
&self.eth_private_key,
)
.await?;
if Some(U64::from(0u64)) == recipt.status {
return Err(GatewayClientError::BurnTokenError(
web3::Error::InvalidResponse(format!(
"Approve transaction status is 0 (failure): {:?}",
recipt.logs,
)),
));
} else {
log::info!(
"Approved {} tokens for bandwidth use on Ethereum",
TOKENS_TO_BURN
);
}
let mut options = Options::default();
let estimation = self
.contract
.estimate_gas(
ETH_BURN_FUNCTION_NAME,
(
Token::Uint(U256::from(UTOKENS_TO_BURN)),
Token::Uint(U256::from(&verification_key.to_bytes())),
Token::Bytes(signed_verification_key.to_bytes().to_vec()),
Token::String(gateway_owner.clone()),
),
SecretKeyRef::from(&self.eth_private_key).address(),
options.clone(),
)
.await?;
options.gas = Some(estimation);
log::info!("Generating bandwidth on ETH contract in 10 seconds with an estimated gas of {}. \
Kill the process if you want to abort. Keep in mind that if you abort now, you'll still have \
some tokens approved for bandwidth spending from the previous action. \
If you don't want that, you'll need to manually decreaseAllowance to revert the approval.", estimation);
#[cfg(not(target_arch = "wasm32"))]
tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
#[cfg(target_arch = "wasm32")]
if let Err(err) = fluvio_wasm_timer::Delay::new(std::time::Duration::from_secs(10)).await {
log::error!(
"the timer has gone away while waiting for possible kill! - {}",
err
);
}
let recipt = self
.contract
.signed_call_with_confirmations(
ETH_BURN_FUNCTION_NAME,
(
Token::Uint(U256::from(UTOKENS_TO_BURN)),
Token::Uint(U256::from(&verification_key.to_bytes())),
Token::Bytes(signed_verification_key.to_bytes().to_vec()),
Token::String(gateway_owner),
),
options,
confirmations,
&self.eth_private_key,
)
.await?;
if Some(U64::from(0u64)) == recipt.status {
Err(GatewayClientError::BurnTokenError(
web3::Error::InvalidResponse(format!(
"Transaction status is 0 (failure): {:?}",
recipt.logs,
)),
))
} else {
log::info!(
"Bought bandwidth on Ethereum: {} MB",
BANDWIDTH_VALUE / 1024 / 1024
);
Ok(())
}
}
}
#[cfg(not(feature = "coconut"))]
#[cfg(test)]
mod tests {
use network_defaults::ETH_EVENT_NAME;
use super::*;
#[test]
fn parse_contract() {
let transport =
Http::new("https://rinkeby.infura.io/v3/00000000000000000000000000000000").unwrap();
let web3 = web3::Web3::new(transport);
// test no panic occurs
eth_contract(web3);
}
#[test]
fn parse_erc20_contract() {
let transport =
Http::new("https://rinkeby.infura.io/v3/00000000000000000000000000000000").unwrap();
let web3 = web3::Web3::new(transport);
// test no panic occurs
eth_erc20_contract(web3);
}
#[test]
fn check_event_name_constant_against_abi() {
let transport =
Http::new("https://rinkeby.infura.io/v3/00000000000000000000000000000000").unwrap();
let web3 = web3::Web3::new(transport);
let contract = eth_contract(web3);
assert!(contract.abi().event(ETH_EVENT_NAME).is_ok());
}
}
@@ -15,8 +15,6 @@ use crate::wasm_storage::PersistentStorage;
use coconut_interface::Credential;
#[cfg(not(target_arch = "wasm32"))]
use credential_storage::PersistentStorage;
#[cfg(not(feature = "coconut"))]
use credentials::token::bandwidth::TokenCredential;
use crypto::asymmetric::identity;
use futures::{FutureExt, SinkExt, StreamExt};
use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes;
@@ -309,6 +307,8 @@ impl GatewayClient {
async {
if let Some(mut s) = m_shutdown {
s.recv().await
} else {
std::future::pending::<()>().await
}
}
.fuse()
@@ -317,7 +317,7 @@ impl GatewayClient {
tokio::pin!(shutdown);
#[cfg(target_arch = "wasm32")]
let mut shutdown = Box::pin(async {}.fuse());
let mut shutdown = std::future::pending::<()>().fuse();
loop {
futures::select! {
@@ -552,30 +552,6 @@ impl GatewayClient {
Ok(())
}
#[cfg(not(feature = "coconut"))]
async fn claim_token_bandwidth(
&mut self,
credential: TokenCredential,
) -> Result<(), GatewayClientError> {
let mut rng = OsRng;
let iv = IV::new_random(&mut rng);
let msg = ClientControlRequest::new_enc_token_bandwidth_credential(
&credential,
self.shared_key.as_ref().unwrap(),
iv,
)
.into();
self.bandwidth_remaining = match self.send_websocket_message(msg).await? {
ServerResponse::Bandwidth { available_total } => Ok(available_total),
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
_ => Err(GatewayClientError::UnexpectedResponse),
}?;
Ok(())
}
async fn try_claim_testnet_bandwidth(&mut self) -> Result<(), GatewayClientError> {
let msg = ClientControlRequest::ClaimFreeTestnetBandwidth.into();
self.bandwidth_remaining = match self.send_websocket_message(msg).await? {
@@ -614,17 +590,10 @@ impl GatewayClient {
.prepare_coconut_credential()
.await?;
#[cfg(not(feature = "coconut"))]
let credential = self
.bandwidth_controller
.as_ref()
.unwrap()
.prepare_token_credential(self.gateway_identity, _gateway_owner)
.await?;
return self.try_claim_testnet_bandwidth().await;
#[cfg(feature = "coconut")]
return self.claim_coconut_bandwidth(credential).await;
#[cfg(not(feature = "coconut"))]
return self.claim_token_bandwidth(credential).await;
}
fn estimate_required_bandwidth(&self, packets: &[MixPacket]) -> i64 {
@@ -113,6 +113,8 @@ impl PartiallyDelegated {
async {
if let Some(mut s) = m_shutdown {
s.recv().await
} else {
std::future::pending::<()>().await
}
}
.fuse()
@@ -121,7 +123,7 @@ impl PartiallyDelegated {
tokio::pin!(shutdown);
#[cfg(target_arch = "wasm32")]
let mut shutdown = Box::pin(async {}.fuse());
let mut shutdown = std::future::pending::<()>().fuse();
let ret_err = loop {
futures::select! {
@@ -26,13 +26,6 @@ pub struct CoconutCredential {
pub signature: String,
}
pub struct ERC20Credential {
pub id: i64,
pub public_key: String,
pub private_key: String,
pub consumed: bool,
}
#[async_trait]
pub trait Storage: Send + Sync {
async fn insert_coconut_credential(
@@ -47,16 +40,6 @@ pub trait Storage: Send + Sync {
async fn get_next_coconut_credential(&self) -> Result<CoconutCredential, StorageError>;
async fn remove_coconut_credential(&self, id: i64) -> Result<(), StorageError>;
async fn insert_erc20_credential(
&self,
public_key: String,
private_key: String,
) -> Result<(), StorageError>;
async fn get_next_erc20_credential(&self) -> Result<ERC20Credential, StorageError>;
async fn consume_erc20_credential(&self, public_key: String) -> Result<(), StorageError>;
}
#[async_trait]
@@ -79,20 +62,4 @@ impl Storage for PersistentStorage {
async fn remove_coconut_credential(&self, _id: i64) -> Result<(), StorageError> {
Err(StorageError::WasmNotSupported)
}
async fn insert_erc20_credential(
&self,
_public_key: String,
_private_key: String,
) -> Result<(), StorageError> {
Err(StorageError::WasmNotSupported)
}
async fn get_next_erc20_credential(&self) -> Result<ERC20Credential, StorageError> {
Err(StorageError::WasmNotSupported)
}
async fn consume_erc20_credential(&self, _public_key: String) -> Result<(), StorageError> {
Err(StorageError::WasmNotSupported)
}
}
@@ -42,6 +42,7 @@ sha2 = { version = "0.9.5", optional = true }
itertools = { version = "0.10", optional = true }
cosmwasm-std = { version = "1.0.0", optional = true }
execute = { path = "../../execute" }
ledger = { path = "../../ledger" }
[dev-dependencies]
ts-rs = "6.1.2"
@@ -19,8 +19,11 @@ use validator_api_requests::models::{MixNodeBondAnnotated, UptimeResponse};
#[cfg(feature = "nymd-client")]
use crate::nymd::{
self, error::NymdError, CosmWasmClient, NymdClient, QueryNymdClient, SigningNymdClient,
WalletNymdClient,
};
#[cfg(feature = "nymd-client")]
use ledger::CosmosLedger;
#[cfg(feature = "nymd-client")]
use mixnet_contract_common::{
mixnode::DelegationEvent, ContractStateParams, Delegation, IdentityKey, Interval,
@@ -28,7 +31,7 @@ use mixnet_contract_common::{
RewardedSetUpdateDetails,
};
#[cfg(feature = "nymd-client")]
use network_defaults::NymNetworkDetails;
use network_defaults::{NymNetworkDetails, COSMOS_DERIVATION_PATH};
#[cfg(feature = "nymd-client")]
use std::collections::{HashMap, HashSet};
@@ -167,6 +170,51 @@ impl Client<SigningNymdClient> {
}
}
#[cfg(feature = "nymd-client")]
impl Client<WalletNymdClient> {
pub fn new_ledger(config: Config) -> Result<Client<WalletNymdClient>, ValidatorClientError> {
let validator_api_client = validator_api::Client::new(config.api_url.clone());
let signer = CosmosLedger::new(
COSMOS_DERIVATION_PATH.parse().unwrap(),
config
.nymd_config
.chain_details
.bech32_account_prefix
.clone(),
)?;
let nymd_client = NymdClient::connect_with_ledger(
config.nymd_config.clone(),
config.nymd_url.as_str(),
signer,
None,
)?;
Ok(Client {
mnemonic: None,
mixnode_page_limit: config.mixnode_page_limit,
gateway_page_limit: config.gateway_page_limit,
mixnode_delegations_page_limit: config.mixnode_delegations_page_limit,
rewarded_set_page_limit: config.rewarded_set_page_limit,
validator_api: validator_api_client,
nymd: nymd_client,
})
}
pub fn change_nymd(&mut self, new_endpoint: Url) -> Result<(), ValidatorClientError> {
self.nymd = NymdClient::connect_with_ledger(
self.nymd.current_config().clone(),
new_endpoint.as_ref(),
self.nymd.ledger_signer(),
None,
)?;
Ok(())
}
pub fn set_nymd_simulated_gas_multiplier(&mut self, multiplier: f32) {
self.nymd.set_simulated_gas_multiplier(multiplier)
}
}
#[cfg(feature = "nymd-client")]
impl Client<QueryNymdClient> {
pub fn new_query(config: Config) -> Result<Client<QueryNymdClient>, ValidatorClientError> {
@@ -19,6 +19,10 @@ pub enum ValidatorClientError {
#[error("There was an issue with the Nymd client - {0}")]
NymdError(#[from] crate::nymd::error::NymdError),
#[cfg(feature = "nymd-client")]
#[error("There was an issue with the Ledger client - {0}")]
LedgerError(#[from] ledger::error::LedgerError),
#[error("No validator API url has been provided")]
NoAPIUrlAvailable,
}
@@ -262,6 +262,7 @@ pub trait CosmWasmClient: rpc::Client {
let broadcasted = CosmWasmClient::broadcast_tx_sync(self, tx).await?;
if broadcasted.code.is_err() {
println!("Error {:?}", broadcasted);
let code_val = broadcasted.code.value();
return Err(NymdError::BroadcastTxErrorDeliverTx {
hash: broadcasted.hash,
@@ -5,13 +5,19 @@ use crate::nymd::error::NymdError;
use crate::nymd::wallet::DirectSecp256k1HdWallet;
use crate::nymd::GasPrice;
use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl};
use ledger::CosmosLedger;
use std::convert::TryInto;
use std::time::Duration;
pub mod client;
mod helpers;
pub mod logs;
pub mod signing_client;
pub mod types;
pub mod wallet_client;
pub(crate) const DEFAULT_BROADCAST_POLLING_RATE: Duration = Duration::from_secs(4);
pub(crate) const DEFAULT_BROADCAST_TIMEOUT: Duration = Duration::from_secs(60);
pub fn connect<U>(endpoint: U) -> Result<HttpClient, NymdError>
where
@@ -31,3 +37,14 @@ where
{
signing_client::Client::connect_with_signer(endpoint, signer, gas_price)
}
pub fn connect_with_ledger<U: Clone>(
endpoint: U,
signer: CosmosLedger,
gas_price: GasPrice,
) -> Result<wallet_client::Client, NymdError>
where
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
{
wallet_client::Client::connect_with_ledger(endpoint, signer, gas_price)
}
@@ -5,22 +5,23 @@ use crate::nymd::cosmwasm_client::client::CosmWasmClient;
use crate::nymd::cosmwasm_client::helpers::{compress_wasm_code, CheckResponse};
use crate::nymd::cosmwasm_client::logs::{self, parse_raw_logs};
use crate::nymd::cosmwasm_client::types::*;
use crate::nymd::cosmwasm_client::{DEFAULT_BROADCAST_POLLING_RATE, DEFAULT_BROADCAST_TIMEOUT};
use crate::nymd::error::NymdError;
use crate::nymd::fee::{Fee, DEFAULT_SIMULATED_GAS_MULTIPLIER};
use crate::nymd::wallet::DirectSecp256k1HdWallet;
use crate::nymd::{Coin, GasAdjustable, GasPrice, TxResponse};
use crate::nymd::{Coin, GasAdjustable, GasPrice, MinimalSigningCosmWasmClient, TxResponse};
use async_trait::async_trait;
use cosmrs::bank::MsgSend;
use cosmrs::distribution::MsgWithdrawDelegatorReward;
use cosmrs::feegrant::{
AllowedMsgAllowance, BasicAllowance, MsgGrantAllowance, MsgRevokeAllowance,
};
use cosmrs::proto::cosmos::tx::signing::v1beta1::SignMode;
use cosmrs::rpc::endpoint::broadcast;
use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl, SimpleRequest};
use cosmrs::staking::{MsgDelegate, MsgUndelegate};
use cosmrs::tx::{self, Msg, SignDoc, SignerInfo};
use cosmrs::{cosmwasm, rpc, AccountId, Any, Tx};
use ledger::CosmosLedger;
use log::debug;
use serde::Serialize;
use sha2::Digest;
@@ -28,40 +29,16 @@ use sha2::Sha256;
use std::convert::TryInto;
use std::time::{Duration, SystemTime};
const DEFAULT_BROADCAST_POLLING_RATE: Duration = Duration::from_secs(4);
const DEFAULT_BROADCAST_TIMEOUT: Duration = Duration::from_secs(60);
fn empty_fee() -> tx::Fee {
tx::Fee {
amount: vec![],
gas_limit: Default::default(),
payer: None,
granter: None,
}
}
fn single_unspecified_signer_auth(
public_key: Option<tx::SignerPublicKey>,
sequence_number: tx::SequenceNumber,
) -> tx::AuthInfo {
tx::SignerInfo {
public_key,
mode_info: tx::ModeInfo::Single(tx::mode_info::Single {
mode: SignMode::Unspecified,
}),
sequence: sequence_number,
}
.auth_info(empty_fee())
}
#[async_trait]
pub trait SigningCosmWasmClient: CosmWasmClient {
pub trait SigningCosmWasmClient: MinimalSigningCosmWasmClient {
fn signer(&self) -> &DirectSecp256k1HdWallet;
fn gas_price(&self) -> &GasPrice;
fn signer_public_key(&self, signer_address: &AccountId) -> Option<tx::SignerPublicKey> {
let signer_accounts = self.signer().try_derive_accounts().ok()?;
let signer_accounts = SigningCosmWasmClient::signer(self)
.try_derive_accounts()
.ok()?;
let account_from_signer = signer_accounts
.iter()
.find(|account| &account.address == signer_address)?;
@@ -69,33 +46,6 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
Some(public_key.into())
}
async fn simulate(
&self,
signer_address: &AccountId,
messages: Vec<Any>,
memo: impl Into<String> + Send + 'static,
) -> Result<SimulateResponse, NymdError> {
let public_key = self.signer_public_key(signer_address);
let sequence_response = self.get_sequence(signer_address).await?;
let partial_tx = Tx {
body: tx::Body::new(messages, memo, 0u32),
auth_info: single_unspecified_signer_auth(public_key, sequence_response.sequence),
signatures: vec![Vec::new()],
};
self.query_simulate(Some(partial_tx), Vec::new()).await
// for completion sake, once we're able to transition into using `tx_bytes`,
// we might want to use something like this instead:
// let tx_raw: tx::Raw = cosmrs::proto::cosmos::tx::v1beta1::TxRaw {
// body_bytes: partial_tx.body.into_bytes().unwrap(),
// auth_info_bytes: partial_tx.auth_info.into_bytes().unwrap(),
// signatures: partial_tx.signatures,
// }
// .into();
// self.query_simulate(None, tx_raw.to_bytes().unwrap()).await
}
async fn upload(
&self,
sender_address: &AccountId,
@@ -541,50 +491,6 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.check_response()
}
// in this particular case we cannot generalise the argument to `&str` due to lifetime constraints
#[allow(clippy::ptr_arg)]
async fn determine_transaction_fee(
&self,
signer_address: &AccountId,
messages: &[Any],
fee: Fee,
memo: &String,
) -> Result<tx::Fee, NymdError> {
let auto_fee = |multiplier: Option<f32>| async move {
debug!("Trying to simulate gas costs...");
// from what I've seen in manual testing, gas estimation does not exist if transaction
// fails to get executed (for example if you send 'BondMixnode" with invalid signature)
let gas_estimation = self
.simulate(signer_address, messages.to_vec(), memo.clone())
.await?
.gas_info
.ok_or(NymdError::GasEstimationFailure)?
.gas_used;
let multiplier = multiplier.unwrap_or(DEFAULT_SIMULATED_GAS_MULTIPLIER);
let gas = gas_estimation.adjust_gas(multiplier);
debug!("Gas estimation: {}", gas_estimation);
debug!("Multiplying the estimation by {}", multiplier);
debug!("Final gas limit used: {}", gas);
let fee = self.gas_price() * gas;
Ok::<tx::Fee, NymdError>(tx::Fee::from_amount_and_gas(fee, gas))
};
let fee = match fee {
Fee::Manual(fee) => fee,
Fee::Auto(multiplier) => auto_fee(multiplier).await?,
Fee::PayerGranterAuto(auto_feegrant) => {
let mut fee = auto_fee(auto_feegrant.gas_adjustment).await?;
fee.payer = auto_feegrant.payer;
fee.granter = auto_feegrant.granter;
fee
}
};
debug!("Fee used for the transaction: {:?}", fee);
Ok(fee)
}
/// Broadcast a transaction, returning immediately.
async fn sign_and_broadcast_async(
&self,
@@ -597,7 +503,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
let fee = self
.determine_transaction_fee(signer_address, &messages, fee, &memo)
.await?;
let tx_raw = self.sign(signer_address, messages, fee, memo).await?;
let tx_raw = SigningCosmWasmClient::sign(self, signer_address, messages, fee, memo).await?;
let tx_bytes = tx_raw
.to_bytes()
.map_err(|_| NymdError::SerializationError("Tx".to_owned()))?;
@@ -617,7 +523,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
let fee = self
.determine_transaction_fee(signer_address, &messages, fee, &memo)
.await?;
let tx_raw = self.sign(signer_address, messages, fee, memo).await?;
let tx_raw = SigningCosmWasmClient::sign(self, signer_address, messages, fee, memo).await?;
let tx_bytes = tx_raw
.to_bytes()
.map_err(|_| NymdError::SerializationError("Tx".to_owned()))?;
@@ -638,7 +544,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.determine_transaction_fee(signer_address, &messages, fee, &memo)
.await?;
let tx_raw = self.sign(signer_address, messages, fee, memo).await?;
let tx_raw = SigningCosmWasmClient::sign(self, signer_address, messages, fee, memo).await?;
let tx_bytes = tx_raw
.to_bytes()
.map_err(|_| NymdError::SerializationError("Tx".to_owned()))?;
@@ -659,7 +565,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.determine_transaction_fee(signer_address, &messages, fee, &memo)
.await?;
let tx_raw = self.sign(signer_address, messages, fee, memo).await?;
let tx_raw = SigningCosmWasmClient::sign(self, signer_address, messages, fee, memo).await?;
let tx_bytes = tx_raw
.to_bytes()
.map_err(|_| NymdError::SerializationError("Tx".to_owned()))?;
@@ -675,7 +581,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
memo: impl Into<String> + Send + 'static,
signer_data: SignerData,
) -> Result<tx::Raw, NymdError> {
let signer_accounts = self.signer().try_derive_accounts()?;
let signer_accounts = SigningCosmWasmClient::signer(self).try_derive_accounts()?;
let account_from_signer = signer_accounts
.iter()
.find(|account| &account.address == signer_address)
@@ -701,8 +607,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
)
.map_err(|_| NymdError::SigningFailure)?;
self.signer()
.sign_direct_with_account(account_from_signer, sign_doc)
SigningCosmWasmClient::signer(self).sign_direct_with_account(account_from_signer, sign_doc)
}
async fn sign(
@@ -786,7 +691,16 @@ impl CosmWasmClient for Client {
}
}
#[async_trait]
impl MinimalSigningCosmWasmClient for Client {
fn signer(&self) -> CosmosLedger {
todo!()
}
fn gas_price(&self) -> &GasPrice {
&self.gas_price
}
}
impl SigningCosmWasmClient for Client {
fn signer(&self) -> &DirectSecp256k1HdWallet {
&self.signer
@@ -0,0 +1,490 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::nymd::cosmwasm_client::client::CosmWasmClient;
use crate::nymd::cosmwasm_client::helpers::CheckResponse;
use crate::nymd::cosmwasm_client::logs::{self, parse_raw_logs};
use crate::nymd::cosmwasm_client::types::*;
use crate::nymd::cosmwasm_client::HttpClient;
use crate::nymd::cosmwasm_client::{DEFAULT_BROADCAST_POLLING_RATE, DEFAULT_BROADCAST_TIMEOUT};
use crate::nymd::fee::GasAdjustable;
use crate::nymd::wallet_client::rpc::SimpleRequest;
use crate::nymd::DEFAULT_SIMULATED_GAS_MULTIPLIER;
use crate::nymd::{
DirectSecp256k1HdWallet, GasPrice, HttpClientUrl, NymdError, SigningCosmWasmClient,
SimulateResponse, TendermintRpcError, TxResponse,
};
use async_trait::async_trait;
use cosmrs::proto::cosmos::tx::signing::v1beta1::SignMode;
use cosmrs::rpc;
use cosmrs::tx::mode_info::Single;
use cosmrs::tx::{self, Gas, ModeInfo, Msg, MsgProto, SignDoc, SignerInfo};
use cosmrs::{AccountId, Any};
use ledger::CosmosLedger;
use log::{debug, info};
use mixnet_contract_common::ExecuteMsg;
use serde::Serialize;
use std::str::FromStr;
use std::time::Duration;
fn single_unspecified_signer_auth(
public_key: Option<tx::SignerPublicKey>,
sequence_number: tx::SequenceNumber,
) -> tx::AuthInfo {
tx::SignerInfo {
public_key,
mode_info: tx::ModeInfo::Single(tx::mode_info::Single {
mode: SignMode::Unspecified,
}),
sequence: sequence_number,
}
.auth_info(empty_fee())
}
fn empty_fee() -> tx::Fee {
tx::Fee {
amount: vec![],
gas_limit: Default::default(),
payer: None,
granter: None,
}
}
#[derive(Debug, Clone)]
pub struct Client {
rpc_client: HttpClient,
signer: CosmosLedger,
gas_price: GasPrice,
broadcast_polling_rate: Duration,
broadcast_timeout: Duration,
}
impl Client {
pub fn connect_with_ledger<U: Clone>(
endpoint: U,
signer: CosmosLedger,
gas_price: GasPrice,
) -> Result<Self, NymdError>
where
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
{
let rpc_client = HttpClient::new(endpoint)?;
Ok(Client {
rpc_client,
signer,
gas_price,
broadcast_polling_rate: DEFAULT_BROADCAST_POLLING_RATE,
broadcast_timeout: DEFAULT_BROADCAST_TIMEOUT,
})
}
pub fn set_broadcast_polling_rate(&mut self, broadcast_polling_rate: Duration) {
self.broadcast_polling_rate = broadcast_polling_rate
}
pub fn set_broadcast_timeout(&mut self, broadcast_timeout: Duration) {
self.broadcast_timeout = broadcast_timeout
}
pub fn signer(&self) -> CosmosLedger {
self.signer.clone()
}
}
#[async_trait]
impl rpc::Client for Client {
async fn perform<R>(&self, request: R) -> Result<R::Response, rpc::Error>
where
R: SimpleRequest,
{
self.rpc_client.perform(request).await
}
}
#[async_trait]
impl CosmWasmClient for Client {
fn broadcast_polling_rate(&self) -> Duration {
self.broadcast_polling_rate
}
fn broadcast_timeout(&self) -> Duration {
self.broadcast_timeout
}
}
impl MinimalSigningCosmWasmClient for Client {
fn signer(&self) -> CosmosLedger {
self.signer.clone()
}
fn gas_price(&self) -> &GasPrice {
&self.gas_price
}
}
impl SigningCosmWasmClient for Client {
fn signer(&self) -> &DirectSecp256k1HdWallet {
todo!()
}
fn gas_price(&self) -> &GasPrice {
&self.gas_price
}
}
#[derive(Debug, Serialize)]
struct Coin {
amount: String,
denom: String,
}
impl From<crate::nymd::Coin> for Coin {
fn from(coin: crate::nymd::Coin) -> Self {
Coin {
amount: coin.amount.to_string(),
denom: coin.denom.to_string(),
}
}
}
impl From<cosmrs::Coin> for Coin {
fn from(coin: cosmrs::Coin) -> Self {
let nymd_coin: crate::nymd::Coin = coin.into();
nymd_coin.into()
}
}
#[derive(Debug, Serialize)]
struct Fee {
amount: Vec<Coin>,
gas: String,
}
#[derive(Debug, Serialize)]
struct SendValue {
amount: Vec<Coin>,
from_address: String,
to_address: String,
}
#[derive(Debug, Serialize)]
struct MsgSend {
#[serde(rename = "type")]
type_url: String,
value: SendValue,
}
#[derive(Debug, Serialize)]
struct SendTransaction {
account_number: String,
chain_id: String,
fee: Fee,
memo: String,
msgs: Vec<MsgSend>,
sequence: String,
}
#[derive(Serialize)]
struct ExecuteContractValue {
contract: String,
funds: Vec<Coin>,
msg: ExecuteMsg,
sender: String,
}
#[derive(Serialize)]
struct MsgExecuteContract {
#[serde(rename = "type")]
type_url: String,
value: ExecuteContractValue,
}
#[derive(Serialize)]
struct ExecuteContractTransaction {
account_number: String,
chain_id: String,
fee: Fee,
memo: String,
msgs: Vec<MsgExecuteContract>,
sequence: String,
}
#[async_trait]
pub trait MinimalSigningCosmWasmClient: CosmWasmClient {
fn signer(&self) -> CosmosLedger;
fn gas_price(&self) -> &GasPrice;
fn signer_public_key(&self, signer_address: &AccountId) -> Option<tx::SignerPublicKey> {
let response = self.signer().get_addr_secp265k1(false).ok()?;
if response.address == signer_address.to_string() {
let verifying_key: cosmrs::crypto::secp256k1::VerifyingKey = response.public_key.into();
let cosmrs_public_key: cosmrs::crypto::PublicKey = verifying_key.into();
Some(cosmrs_public_key.into())
} else {
None
}
}
async fn simulate(
&self,
signer_address: &AccountId,
messages: Vec<Any>,
memo: impl Into<String> + Send + 'static,
) -> Result<SimulateResponse, NymdError> {
let public_key = self.signer_public_key(signer_address);
let sequence_response = self.get_sequence(signer_address).await?;
let partial_tx = cosmrs::Tx {
body: tx::Body::new(messages, memo, 0u32),
auth_info: single_unspecified_signer_auth(public_key, sequence_response.sequence),
signatures: vec![Vec::new()],
};
self.query_simulate(Some(partial_tx), Vec::new()).await
// for completion sake, once we're able to transition into using `tx_bytes`,
// we might want to use something like this instead:
// let tx_raw: tx::Raw = cosmrs::proto::cosmos::tx::v1beta1::TxRaw {
// body_bytes: partial_tx.body.into_bytes().unwrap(),
// auth_info_bytes: partial_tx.auth_info.into_bytes().unwrap(),
// signatures: partial_tx.signatures,
// }
// .into();
// self.query_simulate(None, tx_raw.to_bytes().unwrap()).await
}
// in this particular case we cannot generalise the argument to `&str` due to lifetime constraints
#[allow(clippy::ptr_arg)]
async fn determine_transaction_fee(
&self,
signer_address: &AccountId,
messages: &[Any],
fee: crate::nymd::fee::Fee,
memo: &String,
) -> Result<tx::Fee, NymdError> {
let auto_fee = |multiplier: Option<f32>| async move {
debug!("Trying to simulate gas costs...");
// from what I've seen in manual testing, gas estimation does not exist if transaction
// fails to get executed (for example if you send 'BondMixnode" with invalid signature)
let gas_estimation = self
.simulate(signer_address, messages.to_vec(), memo.clone())
.await?
.gas_info
.ok_or(NymdError::GasEstimationFailure)?
.gas_used;
let multiplier = multiplier.unwrap_or(DEFAULT_SIMULATED_GAS_MULTIPLIER);
let gas = gas_estimation.adjust_gas(multiplier);
debug!("Gas estimation: {}", gas_estimation);
debug!("Multiplying the estimation by {}", multiplier);
debug!("Final gas limit used: {}", gas);
let fee = self.gas_price() * gas;
Ok::<tx::Fee, NymdError>(tx::Fee::from_amount_and_gas(fee, gas))
};
let fee = match fee {
crate::nymd::fee::Fee::Manual(fee) => fee,
crate::nymd::fee::Fee::Auto(multiplier) => auto_fee(multiplier).await?,
crate::nymd::fee::Fee::PayerGranterAuto(auto_feegrant) => {
let mut fee = auto_fee(auto_feegrant.gas_adjustment).await?;
fee.payer = auto_feegrant.payer;
fee.granter = auto_feegrant.granter;
fee
}
};
debug!("Fee used for the transaction: {:?}", fee);
Ok(fee)
}
fn sign(
&self,
messages: String,
cosmrs_messages: Vec<Any>,
fee: tx::Fee,
memo: impl Into<String> + Send + 'static,
signer_data: SignerData,
) -> Result<tx::Raw, NymdError> {
let signature = self
.signer()
.sign_secp265k1(messages)
.expect("Could not sign")
.signature;
let response = self.signer().get_addr_secp265k1(false)?;
let verifying_key: cosmrs::crypto::secp256k1::VerifyingKey = response.public_key.into();
let cosmrs_public_key: cosmrs::crypto::PublicKey = verifying_key.into();
// TODO: WTF HOW IS TIMEOUT_HEIGHT SUPPOSED TO GET DETERMINED?
// IT DOESNT EXIST IN COSMJS!!
// try to set to 0
let timeout_height = 0u32;
let tx_body = tx::Body::new(cosmrs_messages, memo, timeout_height);
let mut signer_info =
SignerInfo::single_direct(Some(cosmrs_public_key), signer_data.sequence);
signer_info.mode_info = ModeInfo::Single(Single {
mode: SignMode::LegacyAminoJson,
});
let auth_info = signer_info.auth_info(fee);
// ideally I'd prefer to have the entire error put into the NymdError::SigningFailure
// but I'm super hesitant to trying to downcast the eyre::Report to cosmrs::error::Error
let sign_doc = SignDoc::new(
&tx_body,
&auth_info,
&signer_data.chain_id,
signer_data.account_number,
)
.map_err(|_| NymdError::SigningFailure)?;
Ok(cosmrs::proto::cosmos::tx::v1beta1::TxRaw {
body_bytes: sign_doc.body_bytes,
auth_info_bytes: sign_doc.auth_info_bytes,
signatures: vec![signature.as_ref().to_vec()],
}
.into())
}
async fn send_tokens(
&self,
sender_address: &AccountId,
recipient_address: &AccountId,
amount: Vec<crate::nymd::Coin>,
fee: crate::nymd::fee::Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<TxResponse, NymdError> {
let cosmrs_msg = cosmrs::bank::MsgSend {
from_address: sender_address.clone(),
to_address: recipient_address.clone(),
amount: amount.iter().cloned().map(Into::into).collect(),
}
.to_any()
.map_err(|_| NymdError::SerializationError("MsgSend".to_owned()))?;
let memo = memo.into();
let fee = self
.determine_transaction_fee(sender_address, &vec![cosmrs_msg.clone()], fee, &memo)
.await?;
let response = self.signer().get_addr_secp265k1(false)?;
let sequence_response = self
.get_sequence(&AccountId::from_str(&response.address).unwrap())
.await?;
let chain_id = self.get_chain_id().await?;
let signer_data = SignerData {
account_number: sequence_response.account_number,
sequence: sequence_response.sequence,
chain_id,
};
let send_msg = MsgSend {
type_url: String::from("cosmos-sdk/MsgSend"),
value: SendValue {
from_address: sender_address.to_string(),
to_address: recipient_address.to_string(),
amount: amount.iter().cloned().map(Into::into).collect(),
},
};
let tx = SendTransaction {
account_number: signer_data.account_number.to_string(),
chain_id: signer_data.chain_id.to_string(),
fee: Fee {
amount: fee.amount.iter().cloned().map(Into::into).collect(),
gas: fee.gas_limit.to_string(),
},
memo: memo.clone(),
msgs: vec![send_msg],
sequence: signer_data.sequence.to_string(),
};
let tx_bytes = self
.sign(
serde_json::to_string(&tx).unwrap(),
vec![cosmrs_msg],
fee,
memo,
signer_data,
)?
.to_bytes()
.map_err(|_| NymdError::SerializationError("Tx".to_owned()))?;
self.broadcast_tx(tx_bytes.into()).await
}
async fn wallet_execute(
&self,
sender_address: &AccountId,
contract_address: &AccountId,
msg: &ExecuteMsg,
fee: crate::nymd::fee::Fee,
memo: impl Into<String> + Send + 'static,
funds: Vec<crate::nymd::Coin>,
) -> Result<ExecuteResult, NymdError> {
let cosmrs_msg = cosmrs::cosmwasm::MsgExecuteContract {
sender: sender_address.clone(),
contract: contract_address.clone(),
msg: serde_json::to_vec(msg)?,
funds: funds.iter().cloned().map(Into::into).collect(),
}
.to_any()
.map_err(|_| NymdError::SerializationError("MsgExecuteContract".to_owned()))?;
let memo = memo.into();
let fee = self
.determine_transaction_fee(sender_address, &vec![cosmrs_msg.clone()], fee, &memo)
.await?;
let response = self.signer().get_addr_secp265k1(false)?;
let sequence_response = self
.get_sequence(&AccountId::from_str(&response.address).unwrap())
.await?;
let chain_id = self.get_chain_id().await?;
let signer_data = SignerData {
account_number: sequence_response.account_number,
sequence: sequence_response.sequence,
chain_id,
};
let execute_contract_msg = MsgExecuteContract {
type_url: String::from("wasm/MsgExecuteContract"),
value: ExecuteContractValue {
contract: contract_address.to_string(),
funds: funds.iter().cloned().map(Into::into).collect(),
msg: msg.clone(),
sender: sender_address.to_string(),
},
};
let tx = ExecuteContractTransaction {
account_number: signer_data.account_number.to_string(),
chain_id: signer_data.chain_id.to_string(),
fee: Fee {
amount: fee.amount.iter().cloned().map(Into::into).collect(),
gas: fee.gas_limit.to_string(),
},
memo: memo.clone(),
msgs: vec![execute_contract_msg],
sequence: signer_data.sequence.to_string(),
};
let tx_bytes = self
.sign(
serde_json::to_string(&tx).unwrap(),
vec![cosmrs_msg],
fee,
memo,
signer_data,
)?
.to_bytes()
.map_err(|_| NymdError::SerializationError("Tx".to_owned()))?;
let tx_res = self.broadcast_tx(tx_bytes.into()).await?.check_response()?;
let gas_info = GasInfo::new(tx_res.tx_result.gas_wanted, tx_res.tx_result.gas_used);
Ok(ExecuteResult {
logs: parse_raw_logs(tx_res.tx_result.log)?,
data: tx_res.tx_result.data,
transaction_hash: tx_res.hash,
gas_info,
})
}
}
@@ -133,6 +133,9 @@ pub enum NymdError {
#[error("Account had an unexpected bech32 prefix. Expected: {expected}, got: {got}")]
UnexpectedBech32Prefix { got: String, expected: String },
#[error("Ledger error: {0}")]
LedgerError(#[from] ledger::error::LedgerError),
}
impl NymdError {
@@ -6,6 +6,7 @@ use crate::nymd::cosmwasm_client::types::{
Account, ChangeAdminResult, ContractCodeId, ExecuteResult, InstantiateOptions,
InstantiateResult, MigrateResult, SequenceResponse, SimulateResponse, UploadResult,
};
use crate::nymd::cosmwasm_client::wallet_client;
use crate::nymd::error::NymdError;
use crate::nymd::fee::DEFAULT_SIMULATED_GAS_MULTIPLIER;
use crate::nymd::wallet::DirectSecp256k1HdWallet;
@@ -28,12 +29,14 @@ use mixnet_contract_common::{
};
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
use std::str::FromStr;
use std::time::SystemTime;
use vesting_contract_common::ExecuteMsg as VestingExecuteMsg;
use vesting_contract_common::QueryMsg as VestingQueryMsg;
pub use crate::nymd::cosmwasm_client::client::CosmWasmClient;
pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient;
pub use crate::nymd::cosmwasm_client::wallet_client::MinimalSigningCosmWasmClient;
pub use crate::nymd::fee::Fee;
pub use coin::Coin;
pub use cosmrs::bank::MsgSend;
@@ -52,9 +55,11 @@ pub use cosmrs::Coin as CosmosCoin;
pub use cosmrs::{bip32, AccountId, Decimal, Denom};
pub use cosmwasm_std::Coin as CosmWasmCoin;
pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment};
use network_defaults::{ChainDetails, NymNetworkDetails};
use ledger::CosmosLedger;
use network_defaults::{ChainDetails, NymNetworkDetails, COSMOS_DERIVATION_PATH};
pub use signing_client::Client as SigningNymdClient;
pub use traits::{VestingQueryClient, VestingSigningClient};
pub use wallet_client::Client as WalletNymdClient;
pub mod coin;
pub mod cosmwasm_client;
@@ -209,6 +214,34 @@ impl NymdClient<SigningNymdClient> {
}
}
impl NymdClient<WalletNymdClient> {
pub fn connect_with_ledger<U: Clone>(
config: Config,
endpoint: U,
signer: CosmosLedger,
gas_price: Option<GasPrice>,
) -> Result<NymdClient<WalletNymdClient>, NymdError>
where
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
{
let denom = &config.chain_details.mix_denom.base;
let client_address =
AccountId::from_str(&signer.get_addr_secp265k1(false).unwrap().address).unwrap();
let gas_price = gas_price.unwrap_or(GasPrice::new_with_default_price(denom)?);
Ok(NymdClient {
client: WalletNymdClient::connect_with_ledger(endpoint, signer, gas_price)?,
config,
client_address: Some(vec![client_address]),
simulated_gas_multiplier: DEFAULT_SIMULATED_GAS_MULTIPLIER,
})
}
pub fn ledger_signer(&self) -> CosmosLedger {
self.client.signer()
}
}
impl<C> NymdClient<C> {
pub fn current_config(&self) -> &Config {
&self.config
@@ -332,7 +365,7 @@ impl<C> NymdClient<C> {
pub fn address(&self) -> &AccountId
where
C: SigningCosmWasmClient,
C: MinimalSigningCosmWasmClient,
{
// if this is a signing client (as required by the trait bound), it must have the address set
&self.client_address.as_ref().unwrap()[0]
@@ -342,14 +375,14 @@ impl<C> NymdClient<C> {
where
C: SigningCosmWasmClient,
{
self.client.signer()
SigningCosmWasmClient::signer(&self.client)
}
pub fn gas_price(&self) -> &GasPrice
where
C: SigningCosmWasmClient,
C: MinimalSigningCosmWasmClient,
{
self.client.gas_price()
MinimalSigningCosmWasmClient::gas_price(&self.client)
}
pub fn gas_adjustment(&self) -> GasAdjustment {
@@ -896,6 +929,29 @@ impl<C> NymdClient<C> {
) -> Result<TxResponse, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
SigningCosmWasmClient::send_tokens(
&self.client,
self.address(),
recipient,
amount,
fee,
memo,
)
.await
}
/// Send funds from one address to another
pub async fn wallet_send(
&self,
recipient: &AccountId,
amount: Vec<Coin>,
memo: impl Into<String> + Send + 'static,
fee: Option<Fee>,
) -> Result<TxResponse, NymdError>
where
C: MinimalSigningCosmWasmClient + Sync,
{
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
self.client
@@ -980,6 +1036,23 @@ impl<C> NymdClient<C> {
.await
}
pub async fn wallet_execute(
&self,
contract_address: &AccountId,
msg: &ExecuteMsg,
fee: Option<Fee>,
memo: impl Into<String> + Send + 'static,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NymdError>
where
C: MinimalSigningCosmWasmClient + Sync,
{
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
self.client
.wallet_execute(self.address(), contract_address, msg, fee, memo, funds)
.await
}
pub async fn execute_multiple<I, M>(
&self,
contract_address: &AccountId,
@@ -57,6 +57,7 @@ impl Client {
V: AsRef<str>,
{
let url = create_api_url(&self.url, path, params);
log::trace!("url: {:?}", url.as_str());
Ok(self.reqwest_client.get(url).send().await?.json().await?)
}
-1
View File
@@ -9,7 +9,6 @@ edition = "2021"
[dependencies]
cfg-if = "1.0.0"
handlebars = "3.0.1"
humantime-serde = "1.0"
log = "0.4"
serde = { version = "1.0", features = ["derive"] }
toml = "0.5.6"
@@ -18,7 +18,7 @@ fixed = { version = "1.1", features = ["serde"] }
az = "1.1"
log = "0.4.14"
time = { version = "0.3.6", features = ["parsing", "formatting"] }
ts-rs = "6.1.2"
ts-rs = {version = "6.1.2", optional = true}
contracts-common = { path = "../contracts-common" }
@@ -27,4 +27,4 @@ time = { version = "0.3.5", features = ["serde", "macros"] }
[features]
default = []
generate-ts = []
generate-ts = ['ts-rs']
@@ -3,16 +3,12 @@ name = "vesting-contract-common"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cosmwasm-std = "1.0.0"
mixnet-contract-common = { path = "../mixnet-contract" }
serde = { version = "1.0", features = ["derive"] }
schemars = "0.8"
cw-storage-plus = "0.13.4"
config = { path = "../../config" }
ts-rs = "6.1.2"
ts-rs = {version = "6.1.2", optional = true}
[features]
generate-ts = []
generate-ts = ["ts-rs"]
-1
View File
@@ -7,7 +7,6 @@ edition = "2021"
[dependencies]
async-trait = { version = "0.1.51" }
nymcoconut = { path = "../nymcoconut" }
log = "0.4"
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"]}
-51
View File
@@ -17,55 +17,4 @@ impl ERC20CredentialManager {
pub(crate) fn new(connection_pool: sqlx::SqlitePool) -> Self {
ERC20CredentialManager { connection_pool }
}
/// Inserts provided signature into the database.
///
/// # Arguments
///
/// * `public_key`: Base58 representation of a public key.
/// * `private_key`: Base58 representation of a private key.
pub(crate) async fn insert_erc20_credential(
&self,
public_key: String,
private_key: String,
) -> Result<(), sqlx::Error> {
sqlx::query!(
"INSERT INTO erc20_credentials(public_key, private_key, consumed) VALUES (?, ?, ?)",
public_key,
private_key,
false,
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
/// Tries to retrieve one of the stored, unused credentials.
pub(crate) async fn get_next_erc20_credential(&self) -> Result<ERC20Credential, sqlx::Error> {
sqlx::query_as!(
ERC20Credential,
"SELECT * FROM erc20_credentials WHERE consumed = false"
)
.fetch_one(&self.connection_pool)
.await
}
/// Mark a credential as being consumed.
pub(crate) async fn consume_erc20_credential(
&self,
public_key: String,
) -> Result<(), sqlx::Error> {
sqlx::query!(
r#"
UPDATE erc20_credentials
SET consumed = true
WHERE public_key = ?
"#,
public_key
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
}
+1 -35
View File
@@ -4,18 +4,16 @@
*/
use crate::coconut::CoconutCredentialManager;
use crate::erc20::ERC20CredentialManager;
use crate::error::StorageError;
use crate::storage::Storage;
use crate::models::{CoconutCredential, ERC20Credential};
use crate::models::CoconutCredential;
use async_trait::async_trait;
use log::{debug, error};
use sqlx::ConnectOptions;
use std::path::{Path, PathBuf};
mod coconut;
mod erc20;
pub mod error;
mod models;
pub mod storage;
@@ -24,7 +22,6 @@ pub mod storage;
#[derive(Clone)]
pub struct PersistentStorage {
coconut_credential_manager: CoconutCredentialManager,
erc20_credential_manager: ERC20CredentialManager,
}
impl PersistentStorage {
@@ -60,7 +57,6 @@ impl PersistentStorage {
Ok(PersistentStorage {
coconut_credential_manager: CoconutCredentialManager::new(connection_pool.clone()),
erc20_credential_manager: ERC20CredentialManager::new(connection_pool),
})
}
}
@@ -104,36 +100,6 @@ impl Storage for PersistentStorage {
Ok(())
}
async fn insert_erc20_credential(
&self,
public_key: String,
private_key: String,
) -> Result<(), StorageError> {
self.erc20_credential_manager
.insert_erc20_credential(public_key, private_key)
.await?;
Ok(())
}
async fn get_next_erc20_credential(&self) -> Result<ERC20Credential, StorageError> {
let credential = self
.erc20_credential_manager
.get_next_erc20_credential()
.await?;
Ok(credential)
}
async fn consume_erc20_credential(&self, public_key: String) -> Result<(), StorageError> {
let credential = self
.erc20_credential_manager
.consume_erc20_credential(public_key)
.await?;
Ok(credential)
}
}
pub async fn initialise_storage(path: PathBuf) -> PersistentStorage {
-8
View File
@@ -10,11 +10,3 @@ pub struct CoconutCredential {
pub binding_number: String,
pub signature: String,
}
pub struct ERC20Credential {
#[allow(dead_code)]
pub id: i64,
pub public_key: String,
pub private_key: String,
pub consumed: bool,
}
+1 -19
View File
@@ -3,7 +3,7 @@
use async_trait::async_trait;
use crate::models::{CoconutCredential, ERC20Credential};
use crate::models::CoconutCredential;
use crate::StorageError;
#[async_trait]
@@ -31,22 +31,4 @@ pub trait Storage: Send + Sync {
///
/// * `signature`: Coconut credential in the form of a signature.
async fn remove_coconut_credential(&self, id: i64) -> Result<(), StorageError>;
/// Inserts provided signature into the database.
///
/// # Arguments
///
/// * `public_key`: Base58 representation of a public key.
/// * `private_key`: Base58 representation of a private key.
async fn insert_erc20_credential(
&self,
public_key: String,
private_key: String,
) -> Result<(), StorageError>;
/// Tries to retrieve one of the stored, unused credential data.
async fn get_next_erc20_credential(&self) -> Result<ERC20Credential, StorageError>;
/// Mark a credential as being consumed.
async fn consume_erc20_credential(&self, public_key: String) -> Result<(), StorageError>;
}
-1
View File
@@ -26,7 +26,6 @@ subtle-encoding = { version = "0.5", features = ["bech32-preview"]}
# internal
nymsphinx-types = { path = "../nymsphinx/types" }
pemstore = { path = "../../common/pemstore" }
config = { path="../../common/config" }
[dev-dependencies]
rand_chacha = "0.2"
+6 -1
View File
@@ -17,6 +17,11 @@ pub fn execute(attr: TokenStream, item: TokenStream) -> TokenStream {
} else {
panic!("Only `mixnet` and `vesting` targets are supported!")
};
let exe = if target == "mixnet" {
quote!(wallet_execute)
} else {
quote!(execute)
};
let cl = proc_macro::TokenStream::from(cl);
let cl = parse_macro_input!(cl as ExprMethodCall);
@@ -85,7 +90,7 @@ pub fn execute(attr: TokenStream, item: TokenStream) -> TokenStream {
let (req, fee) = self.#name(#(#execute_args),*);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
self.client
.execute(
.#exe(
self.address(),
#cl,
&req,
+13
View File
@@ -0,0 +1,13 @@
[package]
name = "ledger"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bip32 = "0.3.0"
k256 = "0.10.4"
ledger-transport = "0.10.0"
ledger-transport-hid = "0.10.0"
thiserror = "1"
+40
View File
@@ -0,0 +1,40 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::LedgerError;
use crate::helpers::answer_bytes;
use bip32::{PublicKey, PublicKeyBytes};
use ledger_transport::APDUAnswer;
/// SECP265K1 address of the device.
pub struct AddrSecp265k1Response {
/// SECP265K1 public key.
pub public_key: k256::PublicKey,
/// String representation of the Cosmos address.
pub address: String,
}
impl TryFrom<APDUAnswer<Vec<u8>>> for AddrSecp265k1Response {
type Error = LedgerError;
fn try_from(answer: APDUAnswer<Vec<u8>>) -> Result<Self, Self::Error> {
let bytes = answer_bytes(&answer)?;
if bytes.len() < 33 {
return Err(Self::Error::InvalidAnswerLength {
expected: 33,
received: bytes.len(),
});
}
let (pub_key, addr) = bytes.split_at(33);
let public_key = k256::PublicKey::from_bytes(
PublicKeyBytes::try_from(pub_key).expect("Public key should be 33 bytes"),
)?;
let address = String::from_utf8(addr.to_vec()).unwrap();
Ok(AddrSecp265k1Response {
public_key,
address,
})
}
}
+37
View File
@@ -0,0 +1,37 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use thiserror::Error;
pub(crate) type Result<T> = std::result::Result<T, LedgerError>;
/// Ledger specific errors.
#[derive(Debug, Error)]
pub enum LedgerError {
#[error("HID API - {0}")]
HidAPI(#[from] ledger_transport_hid::hidapi::HidError),
#[error("HID transport - {0}")]
HidTransport(#[from] ledger_transport_hid::LedgerHIDError),
#[error("Unknown error code - {err_code}")]
UnknownErrorCode { err_code: u16 },
#[error("APDU error - {reason}")]
APDU { reason: String },
#[error("Not enough bytes in answer. Expected at least {expected}, received {received}")]
InvalidAnswerLength { expected: usize, received: usize },
#[error("Not enough components in derivation path. Expected {expected}, received {received}")]
InvalidDerivationPath { expected: usize, received: usize },
#[error("Bip32 - {0}")]
Bip32(#[from] bip32::Error),
#[error("Signature error - {0}")]
Signature(#[from] k256::ecdsa::Error),
#[error("No message found for signing transaction")]
NoMessageFound,
}
+37
View File
@@ -0,0 +1,37 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::{LedgerError, Result};
use bip32::DerivationPath;
use ledger_transport::{APDUAnswer, APDUErrorCode};
pub(crate) fn answer_bytes(answer: &APDUAnswer<Vec<u8>>) -> Result<&[u8]> {
let error_code = answer
.error_code()
.map_err(|err_code| LedgerError::UnknownErrorCode { err_code })?;
match error_code {
APDUErrorCode::NoError => Ok(answer.data()),
e => Err(LedgerError::APDU {
reason: e.description(),
}),
}
}
pub(crate) fn path_bytes(path: DerivationPath) -> Result<[[u8; 4]; 5]> {
let received = path.len();
let components: Vec<[u8; 4]> = path.into_iter().map(|c| c.0.to_le_bytes()).collect();
if components.len() != 5 {
Err(LedgerError::InvalidDerivationPath {
expected: 5,
received,
})
} else {
Ok([
components[0],
components[1],
components[2],
components[3],
components[4],
])
}
}
+140
View File
@@ -0,0 +1,140 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod addr_secp265k1;
pub mod error;
pub(crate) mod helpers;
mod sign_secp265k1;
pub mod version;
use crate::addr_secp265k1::AddrSecp265k1Response;
use crate::error::LedgerError;
use crate::helpers::path_bytes;
use crate::sign_secp265k1::SignSecp265k1Response;
use crate::version::VersionResponse;
use bip32::DerivationPath;
use error::Result;
use ledger_transport::APDUCommand;
use ledger_transport_hid::hidapi::HidApi;
use ledger_transport_hid::TransportNativeHID;
use std::fmt::Debug;
use std::fmt::Formatter;
use std::sync::Arc;
const CLA: u8 = 0x55;
const INS_GET_VERSION: u8 = 0x00;
const INS_SIGN_SECP256K1: u8 = 0x02;
const INS_GET_ADDR_SECP256K1: u8 = 0x04;
const PAYLOAD_TYPE_INIT: u8 = 0x00;
const PAYLOAD_TYPE_ADD: u8 = 0x01;
const PAYLOAD_TYPE_LAST: u8 = 0x02;
const CHUNK_SIZE: usize = 250;
/// Manage hardware Ledger device with Cosmos specific operations, as described in the
/// specification: https://github.com/cosmos/ledger-cosmos/blob/main/docs/APDUSPEC.md
#[derive(Clone)]
pub struct CosmosLedger {
path: DerivationPath,
prefix: String,
transport: Arc<TransportNativeHID>,
}
impl Debug for CosmosLedger {
fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
write!(f, "()")
}
}
impl CosmosLedger {
/// Create the connection to the first Ledger device that we can find.
pub fn new(path: DerivationPath, prefix: String) -> Result<Self> {
let api = HidApi::new()?;
let transport = Arc::new(TransportNativeHID::new(&api)?);
Ok(CosmosLedger {
path,
prefix,
transport,
})
}
/// Get the version of the device.
pub fn get_version(&self) -> Result<VersionResponse> {
let command = APDUCommand {
cla: CLA,
ins: INS_GET_VERSION,
p1: 0,
p2: 0,
data: vec![],
};
let response = self.transport.exchange(&command)?;
VersionResponse::try_from(response)
}
/// Get the SECP265K1 address of the device.
pub fn get_addr_secp265k1(&self, display: bool) -> Result<AddrSecp265k1Response> {
let display = if display { 1 } else { 0 };
let components = path_bytes(self.path.clone())?;
let data: Vec<u8> = vec![
[self.prefix.len() as u8].as_slice(),
self.prefix.as_bytes(),
components[0].as_slice(),
components[1].as_slice(),
components[2].as_slice(),
components[3].as_slice(),
components[4].as_slice(),
]
.into_iter()
.flatten()
.copied()
.collect();
let command = APDUCommand {
cla: CLA,
ins: INS_GET_ADDR_SECP256K1,
p1: display,
p2: 0,
data,
};
let response = self.transport.exchange(&command)?;
AddrSecp265k1Response::try_from(response)
}
pub fn sign_secp265k1(&self, message: String) -> Result<SignSecp265k1Response> {
let serialized_path: Vec<u8> = path_bytes(self.path.clone())?
.into_iter()
.flatten()
.collect();
let mut chunks = vec![serialized_path];
if message.is_empty() {
return Err(LedgerError::NoMessageFound);
}
for chunk in message.into_bytes().chunks(CHUNK_SIZE) {
chunks.push(chunk.to_vec());
}
let length = chunks.len();
for (idx, chunk) in chunks.into_iter().enumerate() {
let payload_desc = if idx == 0 {
PAYLOAD_TYPE_INIT
} else if idx + 1 == length {
PAYLOAD_TYPE_LAST
} else {
PAYLOAD_TYPE_ADD
};
let command = APDUCommand {
cla: CLA,
ins: INS_SIGN_SECP256K1,
p1: payload_desc,
p2: 0,
data: chunk,
};
let sign_response = self.transport.exchange(&command)?;
if payload_desc == PAYLOAD_TYPE_LAST {
return SignSecp265k1Response::try_from(sign_response);
}
}
// It should never reach this, as the message is not empty
Err(LedgerError::NoMessageFound)
}
}
+25
View File
@@ -0,0 +1,25 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::LedgerError;
use crate::helpers::answer_bytes;
use k256::ecdsa::Signature;
use ledger_transport::APDUAnswer;
/// Version and status data of the device.
pub struct SignSecp265k1Response {
/// DER encoded signature data
pub signature: Signature,
}
impl TryFrom<APDUAnswer<Vec<u8>>> for SignSecp265k1Response {
type Error = LedgerError;
fn try_from(answer: APDUAnswer<Vec<u8>>) -> Result<Self, Self::Error> {
let bytes = answer_bytes(&answer)?;
Ok(SignSecp265k1Response {
signature: Signature::from_der(bytes)?,
})
}
}
+42
View File
@@ -0,0 +1,42 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::LedgerError;
use crate::helpers::answer_bytes;
use ledger_transport::APDUAnswer;
/// Version and status data of the device.
pub struct VersionResponse {
/// Activation status of test mode.
pub test_mode: bool,
/// Major part of Cosmos application version.
pub major: u8,
/// Minor part of Cosmos application version.
pub minor: u8,
/// Patch part of Cosmos application version.
pub patch: u8,
/// PIN locked status.
pub device_locked: bool,
}
impl TryFrom<APDUAnswer<Vec<u8>>> for VersionResponse {
type Error = LedgerError;
fn try_from(answer: APDUAnswer<Vec<u8>>) -> Result<Self, Self::Error> {
let bytes = answer_bytes(&answer)?;
if bytes.len() != 5 {
return Err(Self::Error::InvalidAnswerLength {
expected: 5,
received: bytes.len(),
});
}
Ok(VersionResponse {
test_mode: bytes[0] != 0,
major: bytes[1],
minor: bytes[2],
patch: bytes[3],
device_locked: bytes[4] != 0,
})
}
}
-2
View File
@@ -8,7 +8,6 @@ edition = "2021"
[dependencies]
bytes = "1.0"
dashmap = "4.0"
futures = "0.3"
humantime-serde = "1.0"
log = "0.4"
@@ -19,7 +18,6 @@ tokio-util = { version = "0.7.3", features = ["codec"] }
url = "2.2"
crypto = { path = "../crypto" }
nonexhaustive-delayqueue = { path = "../nonexhaustive-delayqueue" }
nymsphinx-acknowledgements = { path = "../nymsphinx/acknowledgements" }
nymsphinx-addressing = { path = "../nymsphinx/addressing" }
nymsphinx-forwarding = { path = "../nymsphinx/forwarding" }
-1
View File
@@ -7,7 +7,6 @@ use std::{env::var, path::PathBuf};
use url::Url;
pub mod all;
pub mod eth_contract;
pub mod mainnet;
pub mod qa;
pub mod sandbox;
+2
View File
@@ -1,5 +1,7 @@
Ordered Buffer
==============
**CURRENTLY UNUSED**
This crate takes care of reliably and speedily turning bytes into a series of ordered message fragments on one side, and of reliably reassembling the fragments into the original message on the other.
-1
View File
@@ -8,5 +8,4 @@ edition = "2021"
[dependencies]
nymsphinx-addressing = { path = "../../../common/nymsphinx/addressing" }
ordered-buffer = {path = "../ordered-buffer"}
thiserror = "1"
-1
View File
@@ -8,7 +8,6 @@ edition = "2021"
[dependencies]
log = "0.4"
tokio = { version = "1.19.1", features = ["rt-multi-thread", "net", "signal"] }
tokio-util = { version = "0.7.3", features = ["codec"] }
[dev-dependencies]
tokio = { version = "1.19.1", features = ["rt-multi-thread", "net", "signal", "test-util", "macros"] }
+1
View File
@@ -22,6 +22,7 @@ ts-rs = "6.1.2"
cosmwasm-std = "1.0.0-beta8"
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
ledger = { path = "../../common/ledger" }
validator-client = { path = "../../common/client-libs/validator-client", features = [
"nymd-client",
] }
+6
View File
@@ -70,6 +70,11 @@ pub enum TypesError {
LossyCoinConversion,
#[error("The provided coin has an unknown denomination - {0}")]
UnknownCoinDenom(String),
#[error("{source}")]
LedgerError {
#[from]
source: ledger::error::LedgerError,
},
}
impl Serialize for TypesError {
@@ -88,6 +93,7 @@ impl From<ValidatorClientError> for TypesError {
ValidatorClientError::MalformedUrlProvided(e) => e.into(),
ValidatorClientError::NymdError(e) => e.into(),
ValidatorClientError::NoAPIUrlAvailable => TypesError::NoValidatorApiUrlConfigured,
ValidatorClientError::LedgerError(e) => e.into(),
}
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
## Unreleased
## [nym-contracts-v1.0.2](https://github.com/nymtech/nym/tree/nym-contracts-v1.0.2) (2022-09-13)
### Added
+325 -340
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,5 +1,5 @@
[workspace]
members = ["bandwidth-claim", "coconut-bandwidth", "mixnet", "vesting", "multisig/cw3-flex-multisig", "multisig/cw4-group", "coconut-test"]
members = ["coconut-bandwidth", "mixnet", "vesting", "multisig/cw3-flex-multisig", "multisig/cw4-group", "coconut-test"]
[profile.release]
opt-level = 3
-20
View File
@@ -1,20 +0,0 @@
[package]
name = "bandwidth-claim"
version = "1.0.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib", "rlib"]
[dev-dependencies]
[dependencies]
bandwidth-claim-contract = { path = "../../common/bandwidth-claim-contract" }
cosmwasm-std = "1.0.0"
cosmwasm-storage = "1.0.0"
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = "1.0.23"
-27
View File
@@ -1,27 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{StdError, VerificationError};
use thiserror::Error;
/// Custom errors for contract failure conditions.
///
/// Add any other custom errors you like here.
/// Look at https://docs.rs/thiserror/1.0.21/thiserror/ for details.
#[derive(Error, Debug, PartialEq)]
pub enum ContractError {
#[error("{0}")]
Std(#[from] StdError),
#[error("Invalid size for signature items")]
InvalidSignatureSize,
#[error("This payment has already been claimed by someone")]
PaymentAlreadyClaimed,
#[error("Error while verifying ed25519 signature - {0}")]
VerificationError(#[from] VerificationError),
#[error("The payment is not properly signed")]
BadSignature,
}
-103
View File
@@ -1,103 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod error;
mod queries;
mod storage;
mod support;
mod transactions;
use cosmwasm_std::{
entry_point, to_binary, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response,
};
use crate::error::ContractError;
use bandwidth_claim_contract::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
/// Instantiate the contract.
///
/// `deps` contains Storage, API and Querier
/// `env` contains block, message and contract info
/// `msg` is the contract initialization message, sort of like a constructor call.
#[entry_point]
pub fn instantiate(
_deps: DepsMut<'_>,
_env: Env,
_info: MessageInfo,
_msg: InstantiateMsg,
) -> Result<Response, ContractError> {
Ok(Response::default())
}
/// Handle an incoming message
#[entry_point]
pub fn execute(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::LinkPayment { data } => transactions::link_payment(deps, env, info, data),
}
}
#[entry_point]
pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
let query_res = match msg {
QueryMsg::GetPayments { start_after, limit } => {
to_binary(&queries::query_payments_paged(deps, start_after, limit)?)
}
};
Ok(query_res?)
}
#[entry_point]
pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
Ok(Default::default())
}
#[cfg(test)]
pub mod tests {
use super::*;
use bandwidth_claim_contract::payment::PagedPaymentResponse;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
use cosmwasm_std::{coins, from_binary};
const TEST_MIX_DENOM: &str = "unym";
#[test]
fn initialize_contract() {
let mut deps = mock_dependencies();
let env = mock_env();
let msg = InstantiateMsg {};
let info = mock_info("creator", &[]);
let res = instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
assert_eq!(0, res.messages.len());
// payments should be empty after initialization
let res = query(
deps.as_ref(),
env.clone(),
QueryMsg::GetPayments {
start_after: None,
limit: Option::from(2),
},
)
.unwrap();
let page: PagedPaymentResponse = from_binary(&res).unwrap();
assert_eq!(0, page.payments.len()); // there are no payments in the list when it's just been initialized
// Contract balance should match what we initialized it as
assert_eq!(
coins(0, TEST_MIX_DENOM),
vec![deps
.as_ref()
.querier
.query_balance(env.contract.address, TEST_MIX_DENOM)
.unwrap()]
);
}
}
-191
View File
@@ -1,191 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{Deps, Order, StdResult};
use crate::storage::payments_read;
use bandwidth_claim_contract::keys::PublicKey;
use bandwidth_claim_contract::payment::{PagedPaymentResponse, Payment};
const PAYMENT_PAGE_MAX_LIMIT: u32 = 100;
const PAYMENT_PAGE_DEFAULT_LIMIT: u32 = 50;
/// Adds a 0 byte to terminate the `start_after` value given. This allows CosmWasm
/// to get the succeeding key as the start of the next page.
fn calculate_start_value<B: AsRef<[u8]>>(start_after: Option<B>) -> Option<Vec<u8>> {
start_after.as_ref().map(|identity| {
identity
.as_ref()
.iter()
.cloned()
.chain(std::iter::once(0))
.collect()
})
}
pub fn query_payments_paged(
deps: Deps<'_>,
start_after: Option<PublicKey>,
limit: Option<u32>,
) -> StdResult<PagedPaymentResponse> {
let limit = limit
.unwrap_or(PAYMENT_PAGE_DEFAULT_LIMIT)
.min(PAYMENT_PAGE_MAX_LIMIT) as usize;
let start = calculate_start_value(start_after);
let payments = payments_read(deps.storage)
.range(start.as_deref(), None, Order::Ascending)
.take(limit)
.map(|res| res.map(|item| item.1))
.collect::<StdResult<Vec<Payment>>>()?;
let start_next_after = payments.last().map(|payment| payment.verification_key());
Ok(PagedPaymentResponse::new(payments, limit, start_next_after))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::storage::payments;
use crate::support::tests::helpers;
use std::convert::TryInto;
#[test]
fn payments_empty_on_init() {
let deps = helpers::init_contract();
let response = query_payments_paged(deps.as_ref(), None, Option::from(2)).unwrap();
assert_eq!(0, response.payments.len());
}
#[test]
fn payments_paged_retrieval_obeys_limits() {
let mut deps = helpers::init_contract();
let storage = deps.as_mut().storage;
let limit = 2;
for n in 0u32..10000 {
let bytes: Vec<u8> = std::iter::repeat(n.to_be_bytes())
.take(8)
.flatten()
.collect();
let verification_key = PublicKey::new(bytes.try_into().unwrap());
let payment = helpers::payment_fixture();
payments(storage)
.save(&verification_key.to_bytes(), &payment)
.unwrap();
}
let page1 = query_payments_paged(deps.as_ref(), None, Option::from(limit)).unwrap();
assert_eq!(limit, page1.payments.len() as u32);
}
#[test]
fn payments_paged_retrieval_has_default_limit() {
let mut deps = helpers::init_contract();
let storage = deps.as_mut().storage;
for n in 0u32..100 {
let bytes: Vec<u8> = std::iter::repeat(n.to_be_bytes())
.take(8)
.flatten()
.collect();
let verification_key = PublicKey::new(bytes.try_into().unwrap());
let payment = helpers::payment_fixture();
payments(storage)
.save(&verification_key.to_bytes(), &payment)
.unwrap();
}
// query without explicitly setting a limit
let page1 = query_payments_paged(deps.as_ref(), None, None).unwrap();
assert_eq!(PAYMENT_PAGE_DEFAULT_LIMIT, page1.payments.len() as u32);
}
#[test]
fn payments_paged_retrieval_has_max_limit() {
let mut deps = helpers::init_contract();
let storage = deps.as_mut().storage;
for n in 0u32..10000 {
let bytes: Vec<u8> = std::iter::repeat(n.to_be_bytes())
.take(8)
.flatten()
.collect();
let verification_key = PublicKey::new(bytes.try_into().unwrap());
let payment = helpers::payment_fixture();
payments(storage)
.save(&verification_key.to_bytes(), &payment)
.unwrap();
}
// query with a crazily high limit in an attempt to use too many resources
let crazy_limit = 1000;
let page1 = query_payments_paged(deps.as_ref(), None, Option::from(crazy_limit)).unwrap();
// we default to a decent sized upper bound instead
assert_eq!(PAYMENT_PAGE_MAX_LIMIT, page1.payments.len() as u32);
}
#[test]
fn payments_pagination_works() {
let key1 = PublicKey::new([1; 32]);
let key2 = PublicKey::new([2; 32]);
let key3 = PublicKey::new([3; 32]);
let key4 = PublicKey::new([4; 32]);
let mut deps = helpers::init_contract();
let payment = helpers::payment_fixture();
payments(&mut deps.storage)
.save(&key1.to_bytes(), &payment)
.unwrap();
let per_page = 2;
let page1 = query_payments_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
// page should have 1 result on it
assert_eq!(1, page1.payments.len());
// save another
payments(&mut deps.storage)
.save(&key2.to_bytes(), &payment)
.unwrap();
// page1 should have 2 results on it
let page1 = query_payments_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
assert_eq!(2, page1.payments.len());
payments(&mut deps.storage)
.save(&key3.to_bytes(), &payment)
.unwrap();
// page1 still has 2 results
let page1 = query_payments_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
assert_eq!(2, page1.payments.len());
// retrieving the next page should start after the last key on this page
let start_after = key2;
let page2 = query_payments_paged(
deps.as_ref(),
Option::from(start_after),
Option::from(per_page),
)
.unwrap();
assert_eq!(1, page2.payments.len());
// save another one
payments(&mut deps.storage)
.save(&key4.to_bytes(), &payment)
.unwrap();
let start_after = key2;
let page2 = query_payments_paged(
deps.as_ref(),
Option::from(start_after),
Option::from(per_page),
)
.unwrap();
// now we have 2 pages, with 2 results on the second page
assert_eq!(2, page2.payments.len());
}
}
-79
View File
@@ -1,79 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::Storage;
use cosmwasm_storage::{bucket, bucket_read, Bucket, ReadonlyBucket};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use bandwidth_claim_contract::payment::Payment;
// buckets
const PREFIX_PAYMENTS: &[u8] = b"payments";
const PREFIX_STATUS: &[u8] = b"status";
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub enum Status {
Unchecked,
Checked,
Spent,
}
pub fn payments(storage: &mut dyn Storage) -> Bucket<'_, Payment> {
bucket(storage, PREFIX_PAYMENTS)
}
pub fn payments_read(storage: &dyn Storage) -> ReadonlyBucket<'_, Payment> {
bucket_read(storage, PREFIX_PAYMENTS)
}
pub fn status(storage: &mut dyn Storage) -> Bucket<'_, Status> {
bucket(storage, PREFIX_STATUS)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::support::tests::helpers;
use bandwidth_claim_contract::keys::PublicKey;
use cosmwasm_std::testing::MockStorage;
#[test]
fn payments_single_read_retrieval() {
let mut storage = MockStorage::new();
let key1 = PublicKey::new([1; 32]);
let key2 = PublicKey::new([2; 32]);
let payment1 = helpers::payment_fixture();
let payment2 = helpers::payment_fixture();
payments(&mut storage)
.save(key1.as_ref(), &payment1)
.unwrap();
payments(&mut storage)
.save(key2.as_ref(), &payment2)
.unwrap();
let res1 = payments_read(&storage).load(key1.as_ref()).unwrap();
let res2 = payments_read(&storage).load(key2.as_ref()).unwrap();
assert_eq!(payment1, res1);
assert_eq!(payment2, res2);
}
#[test]
fn status_single_read_retrieval() {
let mut storage = MockStorage::new();
let key1 = PublicKey::new([1; 32]);
let key2 = PublicKey::new([2; 32]);
let status_value = Status::Unchecked;
status(&mut storage)
.save(key1.as_ref(), &status_value)
.unwrap();
status(&mut storage)
.save(key2.as_ref(), &status_value)
.unwrap();
let res1 = status(&mut storage).load(key1.as_ref()).unwrap();
assert_eq!(status_value, res1);
let res2 = status(&mut storage).load(key2.as_ref()).unwrap();
assert_eq!(status_value, res2);
}
}
@@ -1,3 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod tests;
@@ -1,28 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(test)]
pub mod helpers {
use crate::instantiate;
use bandwidth_claim_contract::keys::PublicKey;
use bandwidth_claim_contract::msg::InstantiateMsg;
use bandwidth_claim_contract::payment::Payment;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier};
use cosmwasm_std::{Empty, MemoryStorage, OwnedDeps};
pub fn init_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>> {
let mut deps = mock_dependencies();
let msg = InstantiateMsg {};
let env = mock_env();
let info = mock_info("creator", &[]);
instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
return deps;
}
pub fn payment_fixture() -> Payment {
let public_key = PublicKey::new([1; 32]);
let gateway_identity = PublicKey::new([2; 32]);
let bandwidth = 42;
Payment::new(public_key, gateway_identity, bandwidth)
}
}
@@ -1,146 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{DepsMut, Env, MessageInfo, Response};
use crate::error::ContractError;
use crate::storage::{payments, status, Status};
use bandwidth_claim_contract::payment::{LinkPaymentData, Payment};
pub(crate) fn link_payment(
deps: DepsMut<'_>,
_env: Env,
_info: MessageInfo,
data: LinkPaymentData,
) -> Result<Response, ContractError> {
let mut status_bucket = status(deps.storage);
let verification_key = data.verification_key.to_bytes();
let gateway_identity = data.gateway_identity.to_bytes();
let message: Vec<u8> = verification_key
.iter()
.chain(gateway_identity.iter())
.copied()
.collect();
let signature = data.signature.to_bytes();
if let Ok(Some(_)) = status_bucket.may_load(&verification_key) {
return Err(ContractError::PaymentAlreadyClaimed);
}
if !deps
.api
.ed25519_verify(&message, &signature, &verification_key)?
{
return Err(ContractError::BadSignature);
}
status_bucket.save(&verification_key, &Status::Unchecked)?;
payments(deps.storage).save(
&verification_key,
&Payment::new(data.verification_key, data.gateway_identity, data.bandwidth),
)?;
Ok(Response::default())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::storage::payments_read;
use crate::support::tests::helpers;
use bandwidth_claim_contract::keys::PublicKey;
use cosmwasm_std::testing::{mock_env, mock_info};
#[test]
fn bad_signature_payment() {
let mut deps = helpers::init_contract();
let env = mock_env();
let info = mock_info("owner", &[]);
let payment_data = LinkPaymentData::new([1; 32], [2; 32], 42, [3; 64]);
assert_eq!(
link_payment(deps.as_mut(), env, info, payment_data),
Err(ContractError::BadSignature)
);
}
#[test]
fn good_payment() {
let mut deps = helpers::init_contract();
let env = mock_env();
let info = mock_info("owner", &[]);
let verification_key = [
78, 142, 213, 13, 39, 169, 76, 205, 242, 206, 129, 208, 190, 51, 139, 206, 245, 199,
120, 151, 181, 250, 192, 153, 123, 104, 129, 139, 60, 254, 243, 98,
];
let gateway_identity = [
106, 76, 76, 238, 214, 177, 233, 112, 56, 33, 21, 201, 89, 42, 69, 196, 175, 56, 6,
110, 184, 167, 203, 63, 1, 167, 134, 102, 165, 215, 3, 212,
];
let bandwidth = 42;
let signature = [
200, 134, 156, 198, 113, 180, 129, 90, 70, 28, 176, 201, 35, 208, 145, 28, 15, 16, 9,
110, 148, 188, 193, 75, 157, 201, 206, 211, 128, 215, 66, 207, 175, 155, 48, 24, 171,
254, 9, 37, 108, 205, 143, 37, 77, 189, 162, 52, 44, 130, 173, 60, 220, 22, 193, 3,
111, 90, 123, 147, 206, 8, 137, 1,
];
let payment_data =
LinkPaymentData::new(verification_key, gateway_identity, bandwidth, signature);
assert!(link_payment(deps.as_mut(), env, info, payment_data).is_ok());
assert_eq!(
payments_read(&deps.storage)
.load(&verification_key)
.unwrap(),
Payment::new(
PublicKey::new(verification_key),
PublicKey::new(gateway_identity),
bandwidth
)
);
assert_eq!(
status(&mut deps.storage).load(&verification_key).unwrap(),
Status::Unchecked
)
}
#[test]
fn double_spend_protection() {
let mut deps = helpers::init_contract();
let env = mock_env();
let info = mock_info("owner", &[]);
let verification_key = [
78, 142, 213, 13, 39, 169, 76, 205, 242, 206, 129, 208, 190, 51, 139, 206, 245, 199,
120, 151, 181, 250, 192, 153, 123, 104, 129, 139, 60, 254, 243, 98,
];
let gateway_identity = [
106, 76, 76, 238, 214, 177, 233, 112, 56, 33, 21, 201, 89, 42, 69, 196, 175, 56, 6,
110, 184, 167, 203, 63, 1, 167, 134, 102, 165, 215, 3, 212,
];
let bandwidth = 42;
let signature = [
200, 134, 156, 198, 113, 180, 129, 90, 70, 28, 176, 201, 35, 208, 145, 28, 15, 16, 9,
110, 148, 188, 193, 75, 157, 201, 206, 211, 128, 215, 66, 207, 175, 155, 48, 24, 171,
254, 9, 37, 108, 205, 143, 37, 77, 189, 162, 52, 44, 130, 173, 60, 220, 22, 193, 3,
111, 90, 123, 147, 206, 8, 137, 1,
];
let payment_data =
LinkPaymentData::new(verification_key, gateway_identity, bandwidth, signature);
link_payment(deps.as_mut(), env.clone(), info.clone(), payment_data).unwrap();
// Only the verification key is used for double spending protection, the other data is irrelevant
let second_payment_data = LinkPaymentData::new(verification_key, [1; 32], 10, [2; 64]);
assert_eq!(
link_payment(deps.as_mut(), env, info, second_payment_data),
Err(ContractError::PaymentAlreadyClaimed)
)
}
}
@@ -1,7 +0,0 @@
node_modules
../.env
.to_do.md
#Hardhat files
cache
artifacts
@@ -1,6 +0,0 @@
module.exports = {
skipFiles: [
'CosmosToken.sol',
'Gravity.sol'
]
};
@@ -1,17 +0,0 @@
# Basic Bandwidth Credential Generator
This directory contains the contract and unit tests for the `BandwidthGenerator` smart contract.
This contract allows users to generate Basic Bandwidth Credentials (BBCs) on the Nym cosmos blockchain using ERC20 representations of NYM as payment, utilising the Cosmos Gravity Bridge for cross-chain payment.
BBCs are credentials that will be presented to Gateways by a Nym Client, and represent a certain amount of bandwidth which can be sent through the Nym Mixnet.
By default 1 NYM = 1 GB of bandwidth. The ratio of NYM - bandwidth is denominated in bytes, and represented in the smart contract by the `BytesPerToken` variable. This variable can be adjusted by the contract owner.
The amount of bandwidth bought is calculated according to the following formula:
`(Token amount in 'wei' * BytesPerToken) / 10**18`
## Usage
* `npm install`
* `npx hardhat compile`
* `npx hardhat test`
@@ -1,4 +0,0 @@
{
"_format": "hh-sol-dbg-1",
"buildInfo": "../../../../../build-info/1f95016aa87f8376998977fedfcae017.json"
}
File diff suppressed because one or more lines are too long
@@ -1,4 +0,0 @@
{
"_format": "hh-sol-dbg-1",
"buildInfo": "../../../../../build-info/1f95016aa87f8376998977fedfcae017.json"
}
@@ -1,194 +0,0 @@
{
"_format": "hh-sol-artifact-1",
"contractName": "IERC20",
"sourceName": "@openzeppelin/contracts/token/ERC20/IERC20.sol",
"abi": [
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "address",
"name": "spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
}
],
"bytecode": "0x",
"deployedBytecode": "0x",
"linkReferences": {},
"deployedLinkReferences": {}
}
@@ -1,4 +0,0 @@
{
"_format": "hh-sol-dbg-1",
"buildInfo": "../../../../../../build-info/1f95016aa87f8376998977fedfcae017.json"
}
@@ -1,233 +0,0 @@
{
"_format": "hh-sol-artifact-1",
"contractName": "IERC20Metadata",
"sourceName": "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol",
"abi": [
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "address",
"name": "spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "decimals",
"outputs": [
{
"internalType": "uint8",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "symbol",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
}
],
"bytecode": "0x",
"deployedBytecode": "0x",
"linkReferences": {},
"deployedLinkReferences": {}
}
@@ -1,4 +0,0 @@
{
"_format": "hh-sol-dbg-1",
"buildInfo": "../../../../build-info/1f95016aa87f8376998977fedfcae017.json"
}
@@ -1,10 +0,0 @@
{
"_format": "hh-sol-artifact-1",
"contractName": "Context",
"sourceName": "@openzeppelin/contracts/utils/Context.sol",
"abi": [],
"bytecode": "0x",
"deployedBytecode": "0x",
"linkReferences": {},
"deployedLinkReferences": {}
}
@@ -1,549 +0,0 @@
{
"_format": "hh-sol-cache-2",
"files": {
"/home/max/dev/nymtech/nym/contracts/basic-bandwidth-generation/contracts/BandwidthGenerator.sol": {
"lastModificationDate": 1643129770695,
"contentHash": "642b72e2d50d565db7bc994cace3f5a0",
"sourceName": "contracts/BandwidthGenerator.sol",
"solcConfig": {
"version": "0.8.10",
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers"
],
"": [
"ast"
]
}
}
}
},
"imports": [
"./CosmosToken.sol",
"./Gravity.sol",
"@openzeppelin/contracts/access/Ownable.sol",
"@openzeppelin/contracts/utils/math/SafeMath.sol"
],
"versionPragmas": [
"0.8.10"
],
"artifacts": [
"BandwidthGenerator"
]
},
"/home/max/dev/nymtech/nym/contracts/basic-bandwidth-generation/contracts/CosmosToken.sol": {
"lastModificationDate": 1642773899716,
"contentHash": "fc5dd09fe73bc6cfece970f702a3aba1",
"sourceName": "contracts/CosmosToken.sol",
"solcConfig": {
"version": "0.8.10",
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers"
],
"": [
"ast"
]
}
}
}
},
"imports": [
"@openzeppelin/contracts/token/ERC20/ERC20.sol"
],
"versionPragmas": [
"0.8.10"
],
"artifacts": [
"CosmosERC20"
]
},
"/home/max/dev/nymtech/nym/contracts/basic-bandwidth-generation/contracts/Gravity.sol": {
"lastModificationDate": 1642773899716,
"contentHash": "0d6dae561f7b541bafb892b8593a08ac",
"sourceName": "contracts/Gravity.sol",
"solcConfig": {
"version": "0.8.10",
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers"
],
"": [
"ast"
]
}
}
}
},
"imports": [
"@openzeppelin/contracts/token/ERC20/IERC20.sol",
"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol",
"@openzeppelin/contracts/security/ReentrancyGuard.sol",
"@openzeppelin/contracts/utils/Address.sol",
"@openzeppelin/contracts/utils/cryptography/ECDSA.sol",
"./CosmosToken.sol"
],
"versionPragmas": [
"0.8.10"
],
"artifacts": [
"Gravity"
]
},
"/home/max/dev/nymtech/nym/contracts/basic-bandwidth-generation/node_modules/@openzeppelin/contracts/access/Ownable.sol": {
"lastModificationDate": 1641812554274,
"contentHash": "4fe56b59ced59d87df6b796758f62895",
"sourceName": "@openzeppelin/contracts/access/Ownable.sol",
"solcConfig": {
"version": "0.8.10",
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers"
],
"": [
"ast"
]
}
}
}
},
"imports": [
"../utils/Context.sol"
],
"versionPragmas": [
"^0.8.0"
],
"artifacts": [
"Ownable"
]
},
"/home/max/dev/nymtech/nym/contracts/basic-bandwidth-generation/node_modules/@openzeppelin/contracts/utils/math/SafeMath.sol": {
"lastModificationDate": 1641812554378,
"contentHash": "5365090efc586b728719e562ebfed0d6",
"sourceName": "@openzeppelin/contracts/utils/math/SafeMath.sol",
"solcConfig": {
"version": "0.8.10",
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers"
],
"": [
"ast"
]
}
}
}
},
"imports": [],
"versionPragmas": [
"^0.8.0"
],
"artifacts": [
"SafeMath"
]
},
"/home/max/dev/nymtech/nym/contracts/basic-bandwidth-generation/node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol": {
"lastModificationDate": 1641812553614,
"contentHash": "2cd550cedf51b8d294607dad5023d717",
"sourceName": "@openzeppelin/contracts/token/ERC20/ERC20.sol",
"solcConfig": {
"version": "0.8.10",
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers"
],
"": [
"ast"
]
}
}
}
},
"imports": [
"./IERC20.sol",
"./extensions/IERC20Metadata.sol",
"../../utils/Context.sol"
],
"versionPragmas": [
"^0.8.0"
],
"artifacts": [
"ERC20"
]
},
"/home/max/dev/nymtech/nym/contracts/basic-bandwidth-generation/node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol": {
"lastModificationDate": 1641812554110,
"contentHash": "0eac3e1a83ee62326ca007811285b274",
"sourceName": "@openzeppelin/contracts/token/ERC20/IERC20.sol",
"solcConfig": {
"version": "0.8.10",
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers"
],
"": [
"ast"
]
}
}
}
},
"imports": [],
"versionPragmas": [
"^0.8.0"
],
"artifacts": [
"IERC20"
]
},
"/home/max/dev/nymtech/nym/contracts/basic-bandwidth-generation/node_modules/@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol": {
"lastModificationDate": 1641812554118,
"contentHash": "aa1be06992a99bb7393b26a6af3c4dfb",
"sourceName": "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol",
"solcConfig": {
"version": "0.8.10",
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers"
],
"": [
"ast"
]
}
}
}
},
"imports": [
"../IERC20.sol"
],
"versionPragmas": [
"^0.8.0"
],
"artifacts": [
"IERC20Metadata"
]
},
"/home/max/dev/nymtech/nym/contracts/basic-bandwidth-generation/node_modules/@openzeppelin/contracts/utils/Context.sol": {
"lastModificationDate": 1641812553346,
"contentHash": "851485d5b925529b1a2f34a0be077891",
"sourceName": "@openzeppelin/contracts/utils/Context.sol",
"solcConfig": {
"version": "0.8.10",
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers"
],
"": [
"ast"
]
}
}
}
},
"imports": [],
"versionPragmas": [
"^0.8.0"
],
"artifacts": [
"Context"
]
},
"/home/max/dev/nymtech/nym/contracts/basic-bandwidth-generation/node_modules/@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol": {
"lastModificationDate": 1641812554370,
"contentHash": "d37406082a74a9b6b114de522fcb6349",
"sourceName": "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol",
"solcConfig": {
"version": "0.8.10",
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers"
],
"": [
"ast"
]
}
}
}
},
"imports": [
"../IERC20.sol",
"../../../utils/Address.sol"
],
"versionPragmas": [
"^0.8.0"
],
"artifacts": [
"SafeERC20"
]
},
"/home/max/dev/nymtech/nym/contracts/basic-bandwidth-generation/node_modules/@openzeppelin/contracts/security/ReentrancyGuard.sol": {
"lastModificationDate": 1641812554322,
"contentHash": "53fbff678f378956efcb207fa748eaa6",
"sourceName": "@openzeppelin/contracts/security/ReentrancyGuard.sol",
"solcConfig": {
"version": "0.8.10",
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers"
],
"": [
"ast"
]
}
}
}
},
"imports": [],
"versionPragmas": [
"^0.8.0"
],
"artifacts": [
"ReentrancyGuard"
]
},
"/home/max/dev/nymtech/nym/contracts/basic-bandwidth-generation/node_modules/@openzeppelin/contracts/utils/Address.sol": {
"lastModificationDate": 1641812553270,
"contentHash": "c5f6c4e4df069c789e7d84b4c3011913",
"sourceName": "@openzeppelin/contracts/utils/Address.sol",
"solcConfig": {
"version": "0.8.10",
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers"
],
"": [
"ast"
]
}
}
}
},
"imports": [],
"versionPragmas": [
"^0.8.0"
],
"artifacts": [
"Address"
]
},
"/home/max/dev/nymtech/nym/contracts/basic-bandwidth-generation/node_modules/@openzeppelin/contracts/utils/cryptography/ECDSA.sol": {
"lastModificationDate": 1641812553414,
"contentHash": "197dbfaf7146845fa76331a4c9980e9f",
"sourceName": "@openzeppelin/contracts/utils/cryptography/ECDSA.sol",
"solcConfig": {
"version": "0.8.10",
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers"
],
"": [
"ast"
]
}
}
}
},
"imports": [],
"versionPragmas": [
"^0.8.0"
],
"artifacts": [
"ECDSA"
]
},
"/home/max/dev/nymtech/nym/contracts/basic-bandwidth-generation/contracts/test-contracts/TestCosmosToken.sol": {
"lastModificationDate": 1642773899716,
"contentHash": "957b6079ee0d5a6e048fce8555c14dee",
"sourceName": "contracts/test-contracts/TestCosmosToken.sol",
"solcConfig": {
"version": "0.8.10",
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers"
],
"": [
"ast"
]
}
}
}
},
"imports": [
"@openzeppelin/contracts/token/ERC20/ERC20.sol"
],
"versionPragmas": [
"0.8.10"
],
"artifacts": [
"TestCosmosERC20"
]
},
"/home/max/dev/nymtech/nym/contracts/basic-bandwidth-generation/contracts/test-contracts/TestGravity.sol": {
"lastModificationDate": 1642773899716,
"contentHash": "f24299c3acb20aff23914e16b7ba95e1",
"sourceName": "contracts/test-contracts/TestGravity.sol",
"solcConfig": {
"version": "0.8.10",
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers"
],
"": [
"ast"
]
}
}
}
},
"imports": [
"@openzeppelin/contracts/token/ERC20/IERC20.sol",
"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol",
"@openzeppelin/contracts/security/ReentrancyGuard.sol",
"@openzeppelin/contracts/utils/Address.sol",
"@openzeppelin/contracts/utils/cryptography/ECDSA.sol",
"./TestCosmosToken.sol"
],
"versionPragmas": [
"0.8.10"
],
"artifacts": [
"TestGravity"
]
}
}
}
@@ -1,12 +0,0 @@
{
"rinkeby":
{"NYM_ERC20":"0xe58c12f8bA4a28e87841b10BdEdf80A47F86BA9B",
"BANDWIDTH_GENERATOR":"0xfa2714Bf14EB5Bb887e4A54984C6F7A7e3E6c84b",
"GRAVITY":"0xe58c12f8bA4a28e87841b10BdEdf80A47F86BA9B"},
"mainnet":
{"NYM_ERC20":"0x525A8F6F3Ba4752868cde25164382BfbaE3990e1",
"NYMT":"0xE8883BAeF3869e14E4823F46662e81D4F7d2A81F",
"BANDWIDTH_GENERATOR":"0x3FfEb99acca159A182f35F9944dAf3BF41Ae8165",
"BANDWIDTH_GENERATOR_NYMT":"0xB3BF30DD53044c9050B7309031Bbf26b2cecF3be",
"GRAVITY":"0xa4108aA1Ec4967F8b52220a4f7e94A8201F2D906"}
}
@@ -1,115 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.10;
import "./CosmosToken.sol";
import "./Gravity.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
/**
* @title BandwidthGenerator
* @dev Contract for generating Basic Bandwidth Credentials (BBCs) on the Nym cosmos blockchain,
* using ERC20 representations of NYM as payment. Utilises the Gravity Bridge for cross-chain payment.
*
* Credential generation can be switched on/off by the contract owner.
*
* Credentials represent a certain amount of bandwidth which can be sent through the Nym Mixnet.
* By default 1 NYM = 1 GB of bandwidth. The `BytesPerToken` amount can be adjusted by the contract owner.
* The amount of bandwidth bought is calculated according to the following formula:
* `(Token amount in 'wei' / 10**18) * BytesPerToken`
*/
contract BandwidthGenerator is Ownable {
using SafeMath for uint256;
CosmosERC20 public erc20;
Gravity public gravityBridge;
uint256 public BytesPerToken;
bool public credentialGenerationEnabled;
event BBCredentialPurchased(
uint256 Bandwidth,
uint256 indexed VerificationKey,
bytes SignedVerificationKey,
string CosmosRecipient
);
event RatioChanged(
uint256 indexed NewBytesPerToken
);
event CredentialGenerationSwitch(
bool Enabled
);
modifier checkEnabled() {
require(credentialGenerationEnabled, "BandwidthGenerator: credential generation isn't currently enabled");
_;
}
/**
* @param _erc20 Address of the erc20NYM deployed through the Gravity Bridge.
* @param _gravityBridge Address of the deployed Gravity Bridge.
*/
constructor(CosmosERC20 _erc20, Gravity _gravityBridge) {
require(address(_erc20) != address(0), "BandwidthGenerator: erc20 address cannot be null");
require(address(_gravityBridge) != address(0), "BandwidthGenerator: gravity bridge address cannot be null");
erc20 = _erc20;
gravityBridge = _gravityBridge;
BytesPerToken = 1073741824; // default amount set at deployment: 1 erc20NYM = 1073741824 Bytes = 1GB
credentialGenerationEnabled = true;
}
/**
* @dev Changes amount of Bytes each erc20NYM is tradable for. Can only be called by Owner.
* @param _newBytesPerTokenAmount Amount of Bytes BBC is worth per 1 erc20NYM token.
*/
function changeRatio(uint256 _newBytesPerTokenAmount) public onlyOwner {
require(_newBytesPerTokenAmount != 0, "BandwidthGenerator: price cannot be 0");
BytesPerToken = _newBytesPerTokenAmount;
emit RatioChanged(_newBytesPerTokenAmount);
}
/**
* @dev Switches credential generation on/off. Can only be called by Owner.
* @param _generation Whether credential generation is turned on/off.
*/
function credentialGenerationSwitch(bool _generation) public onlyOwner {
credentialGenerationEnabled = _generation;
emit CredentialGenerationSwitch(_generation);
}
/**
* @dev Function to create a BBC for account owning the verification key on the Nym Cosmos Blockchain
* by transfering erc20NYM via the Gravity Bridge.
* @param _amount Amount of erc20NYM tokens to spend on BBC - denominated in wei.
* @param _verificationKey Verification key of account on Nym blockchain who is purchasing BBC.
* @param _signedVerificationKey Number of erc20NYMs to spend signed by _verificationKey for auth on Cosmos Blockchain.
* @param _cosmosRecipient Address of the recipient of payment on Nym Cosmos Blockchain.
*/
function generateBasicBandwidthCredential(uint256 _amount, uint256 _verificationKey, bytes memory _signedVerificationKey, string calldata _cosmosRecipient) public checkEnabled {
require(_signedVerificationKey.length == 64, "BandwidthGenerator: Signature doesn't have 64 bytes");
erc20.transferFrom(msg.sender, address(this), _amount);
erc20.approve(address(gravityBridge), _amount);
gravityBridge.sendToCosmos(
address(erc20),
_cosmosRecipient,
_amount
);
uint256 bandwidth = bandwidthFromToken(_amount);
emit BBCredentialPurchased(
bandwidth,
_verificationKey,
_signedVerificationKey,
_cosmosRecipient
);
}
function bandwidthFromToken(uint256 _amount) public view returns (uint256) {
uint256 amountMulBytes = _amount.mul(BytesPerToken);
return amountMulBytes.div(10**18);
}
}
@@ -1,35 +0,0 @@
//SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.10;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract CosmosERC20 is ERC20 {
uint256 MAX_UINT = 2**256 - 1;
uint8 private cosmosDecimals;
address private gravityAddress;
// This override ensures we return the proper number of decimals
// for the cosmos token
function decimals() public view virtual override returns (uint8) {
return cosmosDecimals;
}
// This is not an accurate total supply. Instead this is the total supply
// of the given cosmos asset on Ethereum at this moment in time. Keeping
// a totally accurate supply would require constant updates from the Cosmos
// side, while in theory this could be piggy-backed on some existing bridge
// operation it's a lot of complextiy to add so we chose to forgoe it.
function totalSupply() public view virtual override returns (uint256) {
return MAX_UINT - balanceOf(gravityAddress);
}
constructor(
address _gravityAddress,
string memory _name,
string memory _symbol,
uint8 _decimals
) ERC20(_name, _symbol) {
cosmosDecimals = _decimals;
gravityAddress = _gravityAddress;
_mint(_gravityAddress, MAX_UINT);
}
}
@@ -1,663 +0,0 @@
//SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.10;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "./CosmosToken.sol";
error InvalidSignature();
error InvalidValsetNonce(uint256 newNonce, uint256 currentNonce);
error InvalidBatchNonce(uint256 newNonce, uint256 currentNonce);
error InvalidLogicCallNonce(uint256 newNonce, uint256 currentNonce);
error InvalidLogicCallTransfers();
error InvalidLogicCallFees();
error InvalidSendToCosmos();
error IncorrectCheckpoint();
error MalformedNewValidatorSet();
error MalformedCurrentValidatorSet();
error MalformedBatch();
error InsufficientPower(uint256 cumulativePower, uint256 powerThreshold);
error BatchTimedOut();
error LogicCallTimedOut();
// This is being used purely to avoid stack too deep errors
struct LogicCallArgs {
// Transfers out to the logic contract
uint256[] transferAmounts;
address[] transferTokenContracts;
// The fees (transferred to msg.sender)
uint256[] feeAmounts;
address[] feeTokenContracts;
// The arbitrary logic call
address logicContractAddress;
bytes payload;
// Invalidation metadata
uint256 timeOut;
bytes32 invalidationId;
uint256 invalidationNonce;
}
// This is used purely to avoid stack too deep errors
// represents everything about a given validator set
struct ValsetArgs {
// the validators in this set, represented by an Ethereum address
address[] validators;
// the powers of the given validators in the same order as above
uint256[] powers;
// the nonce of this validator set
uint256 valsetNonce;
// the reward amount denominated in the below reward token, can be
// set to zero
uint256 rewardAmount;
// the reward token, should be set to the zero address if not being used
address rewardToken;
}
// This represents a validator signature
struct Signature {
uint8 v;
bytes32 r;
bytes32 s;
}
contract Gravity is ReentrancyGuard {
using SafeERC20 for IERC20;
// The number of 'votes' required to execute a valset
// update or batch execution, set to 2/3 of 2^32
uint256 constant constant_powerThreshold = 2863311530;
// These are updated often
bytes32 public state_lastValsetCheckpoint;
mapping(address => uint256) public state_lastBatchNonces;
mapping(bytes32 => uint256) public state_invalidationMapping;
uint256 public state_lastValsetNonce = 0;
// event nonce zero is reserved by the Cosmos module as a special
// value indicating that no events have yet been submitted
uint256 public state_lastEventNonce = 1;
// This is set once at initialization
bytes32 public immutable state_gravityId;
// TransactionBatchExecutedEvent and SendToCosmosEvent both include the field _eventNonce.
// This is incremented every time one of these events is emitted. It is checked by the
// Cosmos module to ensure that all events are received in order, and that none are lost.
//
// ValsetUpdatedEvent does not include the field _eventNonce because it is never submitted to the Cosmos
// module. It is purely for the use of relayers to allow them to successfully submit batches.
event TransactionBatchExecutedEvent(
uint256 indexed _batchNonce,
address indexed _token,
uint256 _eventNonce
);
event SendToCosmosEvent(
address indexed _tokenContract,
address indexed _sender,
string _destination,
uint256 _amount,
uint256 _eventNonce
);
event ERC20DeployedEvent(
// FYI: Can't index on a string without doing a bunch of weird stuff
string _cosmosDenom,
address indexed _tokenContract,
string _name,
string _symbol,
uint8 _decimals,
uint256 _eventNonce
);
event ValsetUpdatedEvent(
uint256 indexed _newValsetNonce,
uint256 _eventNonce,
uint256 _rewardAmount,
address _rewardToken,
address[] _validators,
uint256[] _powers
);
event LogicCallEvent(
bytes32 _invalidationId,
uint256 _invalidationNonce,
bytes _returnData,
uint256 _eventNonce
);
// TEST FIXTURES
// These are here to make it easier to measure gas usage. They should be removed before production
function testMakeCheckpoint(ValsetArgs calldata _valsetArgs, bytes32 _gravityId) external pure {
makeCheckpoint(_valsetArgs, _gravityId);
}
function testCheckValidatorSignatures(
ValsetArgs calldata _currentValset,
Signature[] calldata _sigs,
bytes32 _theHash,
uint256 _powerThreshold
) external pure {
checkValidatorSignatures(_currentValset, _sigs, _theHash, _powerThreshold);
}
// END TEST FIXTURES
function lastBatchNonce(address _erc20Address) external view returns (uint256) {
return state_lastBatchNonces[_erc20Address];
}
function lastLogicCallNonce(bytes32 _invalidation_id) external view returns (uint256) {
return state_invalidationMapping[_invalidation_id];
}
// Utility function to verify geth style signatures
function verifySig(
address _signer,
bytes32 _theHash,
Signature calldata _sig
) private pure returns (bool) {
bytes32 messageDigest = keccak256(
abi.encodePacked("\x19Ethereum Signed Message:\n32", _theHash)
);
return _signer == ECDSA.recover(messageDigest, _sig.v, _sig.r, _sig.s);
}
// Utility function to determine that a validator set and signatures are well formed
function validateValset(ValsetArgs calldata _valset, Signature[] calldata _sigs) private pure {
// Check that current validators, powers, and signatures (v,r,s) set is well-formed
if (
_valset.validators.length != _valset.powers.length ||
_valset.validators.length != _sigs.length
) {
revert MalformedCurrentValidatorSet();
}
}
// Make a new checkpoint from the supplied validator set
// A checkpoint is a hash of all relevant information about the valset. This is stored by the contract,
// instead of storing the information directly. This saves on storage and gas.
// The format of the checkpoint is:
// h(gravityId, "checkpoint", valsetNonce, validators[], powers[])
// Where h is the keccak256 hash function.
// The validator powers must be decreasing or equal. This is important for checking the signatures on the
// next valset, since it allows the caller to stop verifying signatures once a quorum of signatures have been verified.
function makeCheckpoint(ValsetArgs memory _valsetArgs, bytes32 _gravityId)
private
pure
returns (bytes32)
{
// bytes32 encoding of the string "checkpoint"
bytes32 methodName = 0x636865636b706f696e7400000000000000000000000000000000000000000000;
bytes32 checkpoint = keccak256(
abi.encode(
_gravityId,
methodName,
_valsetArgs.valsetNonce,
_valsetArgs.validators,
_valsetArgs.powers,
_valsetArgs.rewardAmount,
_valsetArgs.rewardToken
)
);
return checkpoint;
}
function checkValidatorSignatures(
// The current validator set and their powers
ValsetArgs calldata _currentValset,
// The current validator's signatures
Signature[] calldata _sigs,
// This is what we are checking they have signed
bytes32 _theHash,
uint256 _powerThreshold
) private pure {
uint256 cumulativePower = 0;
for (uint256 i = 0; i < _currentValset.validators.length; i++) {
// If v is set to 0, this signifies that it was not possible to get a signature from this validator and we skip evaluation
// (In a valid signature, it is either 27 or 28)
if (_sigs[i].v != 0) {
// Check that the current validator has signed off on the hash
if (!verifySig(_currentValset.validators[i], _theHash, _sigs[i])) {
revert InvalidSignature();
}
// Sum up cumulative power
cumulativePower = cumulativePower + _currentValset.powers[i];
// Break early to avoid wasting gas
if (cumulativePower > _powerThreshold) {
break;
}
}
}
// Check that there was enough power
if (cumulativePower <= _powerThreshold) {
revert InsufficientPower(cumulativePower, _powerThreshold);
}
// Success
}
// This updates the valset by checking that the validators in the current valset have signed off on the
// new valset. The signatures supplied are the signatures of the current valset over the checkpoint hash
// generated from the new valset.
// Anyone can call this function, but they must supply valid signatures of constant_powerThreshold of the current valset over
// the new valset.
function updateValset(
// The new version of the validator set
ValsetArgs calldata _newValset,
// The current validators that approve the change
ValsetArgs calldata _currentValset,
// These are arrays of the parts of the current validator's signatures
Signature[] calldata _sigs
) external {
// CHECKS
// Check that the valset nonce is greater than the old one
if (_newValset.valsetNonce <= _currentValset.valsetNonce) {
revert InvalidValsetNonce({
newNonce: _newValset.valsetNonce,
currentNonce: _currentValset.valsetNonce
});
}
// Check that the valset nonce is less than a million nonces forward from the old one
// this makes it difficult for an attacker to lock out the contract by getting a single
// bad validator set through with uint256 max nonce
if (_newValset.valsetNonce > _currentValset.valsetNonce + 1000000) {
revert InvalidValsetNonce({
newNonce: _newValset.valsetNonce,
currentNonce: _currentValset.valsetNonce
});
}
// Check that new validators and powers set is well-formed
if (
_newValset.validators.length != _newValset.powers.length ||
_newValset.validators.length == 0
) {
revert MalformedNewValidatorSet();
}
// Check that current validators, powers, and signatures (v,r,s) set is well-formed
validateValset(_currentValset, _sigs);
// Check cumulative power to ensure the contract has sufficient power to actually
// pass a vote
uint256 cumulativePower = 0;
for (uint256 i = 0; i < _newValset.powers.length; i++) {
cumulativePower = cumulativePower + _newValset.powers[i];
if (cumulativePower > constant_powerThreshold) {
break;
}
}
if (cumulativePower <= constant_powerThreshold) {
revert InsufficientPower({
cumulativePower: cumulativePower,
powerThreshold: constant_powerThreshold
});
}
// Check that the supplied current validator set matches the saved checkpoint
if (makeCheckpoint(_currentValset, state_gravityId) != state_lastValsetCheckpoint) {
revert IncorrectCheckpoint();
}
// Check that enough current validators have signed off on the new validator set
bytes32 newCheckpoint = makeCheckpoint(_newValset, state_gravityId);
checkValidatorSignatures(_currentValset, _sigs, newCheckpoint, constant_powerThreshold);
// ACTIONS
// Stored to be used next time to validate that the valset
// supplied by the caller is correct.
state_lastValsetCheckpoint = newCheckpoint;
// Store new nonce
state_lastValsetNonce = _newValset.valsetNonce;
// Send submission reward to msg.sender if reward token is a valid value
if (_newValset.rewardToken != address(0) && _newValset.rewardAmount != 0) {
IERC20(_newValset.rewardToken).safeTransfer(msg.sender, _newValset.rewardAmount);
}
// LOGS
state_lastEventNonce = state_lastEventNonce + 1;
emit ValsetUpdatedEvent(
_newValset.valsetNonce,
state_lastEventNonce,
_newValset.rewardAmount,
_newValset.rewardToken,
_newValset.validators,
_newValset.powers
);
}
// submitBatch processes a batch of Cosmos -> Ethereum transactions by sending the tokens in the transactions
// to the destination addresses. It is approved by the current Cosmos validator set.
// Anyone can call this function, but they must supply valid signatures of constant_powerThreshold of the current valset over
// the batch.
function submitBatch(
// The validators that approve the batch
ValsetArgs calldata _currentValset,
// These are arrays of the parts of the validators signatures
Signature[] calldata _sigs,
// The batch of transactions
uint256[] calldata _amounts,
address[] calldata _destinations,
uint256[] calldata _fees,
uint256 _batchNonce,
address _tokenContract,
// a block height beyond which this batch is not valid
// used to provide a fee-free timeout
uint256 _batchTimeout
) external nonReentrant {
// CHECKS scoped to reduce stack depth
{
// Check that the batch nonce is higher than the last nonce for this token
if (_batchNonce <= state_lastBatchNonces[_tokenContract]) {
revert InvalidBatchNonce({
newNonce: _batchNonce,
currentNonce: state_lastBatchNonces[_tokenContract]
});
}
// Check that the batch nonce is less than one million nonces forward from the old one
// this makes it difficult for an attacker to lock out the contract by getting a single
// bad batch through with uint256 max nonce
if (_batchNonce > state_lastBatchNonces[_tokenContract] + 1000000) {
revert InvalidBatchNonce({
newNonce: _batchNonce,
currentNonce: state_lastBatchNonces[_tokenContract]
});
}
// Check that the block height is less than the timeout height
if (block.number >= _batchTimeout) {
revert BatchTimedOut();
}
// Check that current validators, powers, and signatures (v,r,s) set is well-formed
validateValset(_currentValset, _sigs);
// Check that the supplied current validator set matches the saved checkpoint
if (makeCheckpoint(_currentValset, state_gravityId) != state_lastValsetCheckpoint) {
revert IncorrectCheckpoint();
}
// Check that the transaction batch is well-formed
if (_amounts.length != _destinations.length || _amounts.length != _fees.length) {
revert MalformedBatch();
}
// Check that enough current validators have signed off on the transaction batch and valset
checkValidatorSignatures(
_currentValset,
_sigs,
// Get hash of the transaction batch and checkpoint
keccak256(
abi.encode(
state_gravityId,
// bytes32 encoding of "transactionBatch"
0x7472616e73616374696f6e426174636800000000000000000000000000000000,
_amounts,
_destinations,
_fees,
_batchNonce,
_tokenContract,
_batchTimeout
)
),
constant_powerThreshold
);
// ACTIONS
// Store batch nonce
state_lastBatchNonces[_tokenContract] = _batchNonce;
{
// Send transaction amounts to destinations
uint256 totalFee;
for (uint256 i = 0; i < _amounts.length; i++) {
IERC20(_tokenContract).safeTransfer(_destinations[i], _amounts[i]);
totalFee = totalFee + _fees[i];
}
// Send transaction fees to msg.sender
IERC20(_tokenContract).safeTransfer(msg.sender, totalFee);
}
}
// LOGS scoped to reduce stack depth
{
state_lastEventNonce = state_lastEventNonce + 1;
emit TransactionBatchExecutedEvent(_batchNonce, _tokenContract, state_lastEventNonce);
}
}
// This makes calls to contracts that execute arbitrary logic
// First, it gives the logic contract some tokens
// Then, it gives msg.senders tokens for fees
// Then, it calls an arbitrary function on the logic contract
// invalidationId and invalidationNonce are used for replay prevention.
// They can be used to implement a per-token nonce by setting the token
// address as the invalidationId and incrementing the nonce each call.
// They can be used for nonce-free replay prevention by using a different invalidationId
// for each call.
function submitLogicCall(
// The validators that approve the call
ValsetArgs calldata _currentValset,
// These are arrays of the parts of the validators signatures
Signature[] calldata _sigs,
LogicCallArgs memory _args
) external nonReentrant {
// CHECKS scoped to reduce stack depth
{
// Check that the call has not timed out
if (block.number >= _args.timeOut) {
revert LogicCallTimedOut();
}
// Check that the invalidation nonce is higher than the last nonce for this invalidation Id
if (state_invalidationMapping[_args.invalidationId] >= _args.invalidationNonce) {
revert InvalidLogicCallNonce({
newNonce: _args.invalidationNonce,
currentNonce: state_invalidationMapping[_args.invalidationId]
});
}
// note the lack of nonce skipping check, it's not needed here since an attacker
// will never be able to fill the invalidationId space, therefore a nonce lockout
// is simply not possible
// Check that current validators, powers, and signatures (v,r,s) set is well-formed
validateValset(_currentValset, _sigs);
// Check that the supplied current validator set matches the saved checkpoint
if (makeCheckpoint(_currentValset, state_gravityId) != state_lastValsetCheckpoint) {
revert IncorrectCheckpoint();
}
if (_args.transferAmounts.length != _args.transferTokenContracts.length) {
revert InvalidLogicCallTransfers();
}
if (_args.feeAmounts.length != _args.feeTokenContracts.length) {
revert InvalidLogicCallFees();
}
}
{
bytes32 argsHash = keccak256(
abi.encode(
state_gravityId,
// bytes32 encoding of "logicCall"
0x6c6f67696343616c6c0000000000000000000000000000000000000000000000,
_args.transferAmounts,
_args.transferTokenContracts,
_args.feeAmounts,
_args.feeTokenContracts,
_args.logicContractAddress,
_args.payload,
_args.timeOut,
_args.invalidationId,
_args.invalidationNonce
)
);
// Check that enough current validators have signed off on the transaction batch and valset
checkValidatorSignatures(
_currentValset,
_sigs,
// Get hash of the transaction batch and checkpoint
argsHash,
constant_powerThreshold
);
}
// ACTIONS
// Update invaldiation nonce
state_invalidationMapping[_args.invalidationId] = _args.invalidationNonce;
// Send tokens to the logic contract
for (uint256 i = 0; i < _args.transferAmounts.length; i++) {
IERC20(_args.transferTokenContracts[i]).safeTransfer(
_args.logicContractAddress,
_args.transferAmounts[i]
);
}
// Make call to logic contract
bytes memory returnData = Address.functionCall(_args.logicContractAddress, _args.payload);
// Send fees to msg.sender
for (uint256 i = 0; i < _args.feeAmounts.length; i++) {
IERC20(_args.feeTokenContracts[i]).safeTransfer(msg.sender, _args.feeAmounts[i]);
}
// LOGS scoped to reduce stack depth
{
state_lastEventNonce = state_lastEventNonce + 1;
emit LogicCallEvent(
_args.invalidationId,
_args.invalidationNonce,
returnData,
state_lastEventNonce
);
}
}
function sendToCosmos(
address _tokenContract,
string calldata _destination,
uint256 _amount
) external nonReentrant {
// we snapshot our current balance of this token
uint256 ourStartingBalance = IERC20(_tokenContract).balanceOf(address(this));
// attempt to transfer the user specified amount
IERC20(_tokenContract).safeTransferFrom(msg.sender, address(this), _amount);
// check what this particular ERC20 implementation actually gave us, since it doesn't
// have to be at all related to the _amount
uint256 ourEndingBalance = IERC20(_tokenContract).balanceOf(address(this));
// a very strange ERC20 may trigger this condition, if we didn't have this we would
// underflow, so it's mostly just an error message printer
if (ourEndingBalance <= ourStartingBalance) {
revert InvalidSendToCosmos();
}
state_lastEventNonce = state_lastEventNonce + 1;
// emit to Cosmos the actual amount our balance has changed, rather than the user
// provided amount. This protects against a small set of wonky ERC20 behavior, like
// burning on send but not tokens that for example change every users balance every day.
emit SendToCosmosEvent(
_tokenContract,
msg.sender,
_destination,
ourEndingBalance - ourStartingBalance,
state_lastEventNonce
);
}
function deployERC20(
string calldata _cosmosDenom,
string calldata _name,
string calldata _symbol,
uint8 _decimals
) external {
// Deploy an ERC20 with entire supply granted to Gravity.sol
CosmosERC20 erc20 = new CosmosERC20(address(this), _name, _symbol, _decimals);
// Fire an event to let the Cosmos module know
state_lastEventNonce = state_lastEventNonce + 1;
emit ERC20DeployedEvent(
_cosmosDenom,
address(erc20),
_name,
_symbol,
_decimals,
state_lastEventNonce
);
}
constructor(
// A unique identifier for this gravity instance to use in signatures
bytes32 _gravityId,
// The validator set, not in valset args format since many of it's
// arguments would never be used in this case
address[] memory _validators,
uint256[] memory _powers
) {
// CHECKS
// Check that validators, powers, and signatures (v,r,s) set is well-formed
if (_validators.length != _powers.length || _validators.length == 0) {
revert MalformedCurrentValidatorSet();
}
// Check cumulative power to ensure the contract has sufficient power to actually
// pass a vote
uint256 cumulativePower = 0;
for (uint256 i = 0; i < _powers.length; i++) {
cumulativePower = cumulativePower + _powers[i];
if (cumulativePower > constant_powerThreshold) {
break;
}
}
if (cumulativePower <= constant_powerThreshold) {
revert InsufficientPower({
cumulativePower: cumulativePower,
powerThreshold: constant_powerThreshold
});
}
ValsetArgs memory _valset;
_valset = ValsetArgs(_validators, _powers, 0, 0, address(0));
bytes32 newCheckpoint = makeCheckpoint(_valset, _gravityId);
// ACTIONS
state_gravityId = _gravityId;
state_lastValsetCheckpoint = newCheckpoint;
// LOGS
emit ValsetUpdatedEvent(
state_lastValsetNonce,
state_lastEventNonce,
0,
address(0),
_validators,
_powers
);
}
}
@@ -1,55 +0,0 @@
//SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.10;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
/**
* This is a slightly modified version of the cosmos erc20 contract
* which I have done for unit testing.
*
* All that has been changed is the MAX_UINT variable to allow
* me to mint some tokens more easily in unit tests, and the
* addition of the public mint() function.
*/
contract TestCosmosERC20 is ERC20 {
/* canonical amount */
// uint256 MAX_UINT = 2**256 - 1;
/* unit testing amount */
uint256 HALF_MAX_UINT = 2**256 / 2;
uint8 private cosmosDecimals;
address private gravityAddress;
// This override ensures we return the proper number of decimals
// for the cosmos token
function decimals() public view virtual override returns (uint8) {
return cosmosDecimals;
}
// This is not an accurate total supply. Instead this is the total supply
// of the given cosmos asset on Ethereum at this moment in time. Keeping
// a totally accurate supply would require constant updates from the Cosmos
// side, while in theory this could be piggy-backed on some existing bridge
// operation it's a lot of complextiy to add so we chose to forgoe it.
function totalSupply() public view virtual override returns (uint256) {
return HALF_MAX_UINT - balanceOf(gravityAddress);
}
constructor(
address _gravityAddress,
string memory _name,
string memory _symbol,
uint8 _decimals
) ERC20(_name, _symbol) {
cosmosDecimals = _decimals;
gravityAddress = _gravityAddress;
_mint(_gravityAddress, HALF_MAX_UINT);
}
// Additional function for our (nym repo) unit tests with bridge
function mintForUnitTesting(address _to, uint _amount) public {
_mint(_to, _amount);
}
}
@@ -1,671 +0,0 @@
//SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.10;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "./TestCosmosToken.sol";
/**
* This is a slightly modified version of the gravity bridge contract
* which I have done for unit testing.
*
* All that has been changed is ___
*/
error InvalidSignature();
error InvalidValsetNonce(uint256 newNonce, uint256 currentNonce);
error InvalidBatchNonce(uint256 newNonce, uint256 currentNonce);
error InvalidLogicCallNonce(uint256 newNonce, uint256 currentNonce);
error InvalidLogicCallTransfers();
error InvalidLogicCallFees();
error InvalidSendToCosmos();
error IncorrectCheckpoint();
error MalformedNewValidatorSet();
error MalformedCurrentValidatorSet();
error MalformedBatch();
error InsufficientPower(uint256 cumulativePower, uint256 powerThreshold);
error BatchTimedOut();
error LogicCallTimedOut();
// This is being used purely to avoid stack too deep errors
struct LogicCallArgs {
// Transfers out to the logic contract
uint256[] transferAmounts;
address[] transferTokenContracts;
// The fees (transferred to msg.sender)
uint256[] feeAmounts;
address[] feeTokenContracts;
// The arbitrary logic call
address logicContractAddress;
bytes payload;
// Invalidation metadata
uint256 timeOut;
bytes32 invalidationId;
uint256 invalidationNonce;
}
// This is used purely to avoid stack too deep errors
// represents everything about a given validator set
struct ValsetArgs {
// the validators in this set, represented by an Ethereum address
address[] validators;
// the powers of the given validators in the same order as above
uint256[] powers;
// the nonce of this validator set
uint256 valsetNonce;
// the reward amount denominated in the below reward token, can be
// set to zero
uint256 rewardAmount;
// the reward token, should be set to the zero address if not being used
address rewardToken;
}
// This represents a validator signature
struct Signature {
uint8 v;
bytes32 r;
bytes32 s;
}
contract TestGravity is ReentrancyGuard {
using SafeERC20 for IERC20;
// The number of 'votes' required to execute a valset
// update or batch execution, set to 2/3 of 2^32
uint256 constant constant_powerThreshold = 2863311530;
// These are updated often
bytes32 public state_lastValsetCheckpoint;
mapping(address => uint256) public state_lastBatchNonces;
mapping(bytes32 => uint256) public state_invalidationMapping;
uint256 public state_lastValsetNonce = 0;
// event nonce zero is reserved by the Cosmos module as a special
// value indicating that no events have yet been submitted
uint256 public state_lastEventNonce = 1;
// This is set once at initialization
bytes32 public immutable state_gravityId;
// TransactionBatchExecutedEvent and SendToCosmosEvent both include the field _eventNonce.
// This is incremented every time one of these events is emitted. It is checked by the
// Cosmos module to ensure that all events are received in order, and that none are lost.
//
// ValsetUpdatedEvent does not include the field _eventNonce because it is never submitted to the Cosmos
// module. It is purely for the use of relayers to allow them to successfully submit batches.
event TransactionBatchExecutedEvent(
uint256 indexed _batchNonce,
address indexed _token,
uint256 _eventNonce
);
event SendToCosmosEvent(
address indexed _tokenContract,
address indexed _sender,
string _destination,
uint256 _amount,
uint256 _eventNonce
);
event ERC20DeployedEvent(
// FYI: Can't index on a string without doing a bunch of weird stuff
string _cosmosDenom,
address indexed _tokenContract,
string _name,
string _symbol,
uint8 _decimals,
uint256 _eventNonce
);
event ValsetUpdatedEvent(
uint256 indexed _newValsetNonce,
uint256 _eventNonce,
uint256 _rewardAmount,
address _rewardToken,
address[] _validators,
uint256[] _powers
);
event LogicCallEvent(
bytes32 _invalidationId,
uint256 _invalidationNonce,
bytes _returnData,
uint256 _eventNonce
);
// TEST FIXTURES
// These are here to make it easier to measure gas usage. They should be removed before production
function testMakeCheckpoint(ValsetArgs calldata _valsetArgs, bytes32 _gravityId) external pure {
makeCheckpoint(_valsetArgs, _gravityId);
}
function testCheckValidatorSignatures(
ValsetArgs calldata _currentValset,
Signature[] calldata _sigs,
bytes32 _theHash,
uint256 _powerThreshold
) external pure {
checkValidatorSignatures(_currentValset, _sigs, _theHash, _powerThreshold);
}
// END TEST FIXTURES
function lastBatchNonce(address _erc20Address) external view returns (uint256) {
return state_lastBatchNonces[_erc20Address];
}
function lastLogicCallNonce(bytes32 _invalidation_id) external view returns (uint256) {
return state_invalidationMapping[_invalidation_id];
}
// Utility function to verify geth style signatures
function verifySig(
address _signer,
bytes32 _theHash,
Signature calldata _sig
) private pure returns (bool) {
bytes32 messageDigest = keccak256(
abi.encodePacked("\x19Ethereum Signed Message:\n32", _theHash)
);
return _signer == ECDSA.recover(messageDigest, _sig.v, _sig.r, _sig.s);
}
// Utility function to determine that a validator set and signatures are well formed
function validateValset(ValsetArgs calldata _valset, Signature[] calldata _sigs) private pure {
// Check that current validators, powers, and signatures (v,r,s) set is well-formed
if (
_valset.validators.length != _valset.powers.length ||
_valset.validators.length != _sigs.length
) {
revert MalformedCurrentValidatorSet();
}
}
// Make a new checkpoint from the supplied validator set
// A checkpoint is a hash of all relevant information about the valset. This is stored by the contract,
// instead of storing the information directly. This saves on storage and gas.
// The format of the checkpoint is:
// h(gravityId, "checkpoint", valsetNonce, validators[], powers[])
// Where h is the keccak256 hash function.
// The validator powers must be decreasing or equal. This is important for checking the signatures on the
// next valset, since it allows the caller to stop verifying signatures once a quorum of signatures have been verified.
function makeCheckpoint(ValsetArgs memory _valsetArgs, bytes32 _gravityId)
private
pure
returns (bytes32)
{
// bytes32 encoding of the string "checkpoint"
bytes32 methodName = 0x636865636b706f696e7400000000000000000000000000000000000000000000;
bytes32 checkpoint = keccak256(
abi.encode(
_gravityId,
methodName,
_valsetArgs.valsetNonce,
_valsetArgs.validators,
_valsetArgs.powers,
_valsetArgs.rewardAmount,
_valsetArgs.rewardToken
)
);
return checkpoint;
}
function checkValidatorSignatures(
// The current validator set and their powers
ValsetArgs calldata _currentValset,
// The current validator's signatures
Signature[] calldata _sigs,
// This is what we are checking they have signed
bytes32 _theHash,
uint256 _powerThreshold
) private pure {
uint256 cumulativePower = 0;
for (uint256 i = 0; i < _currentValset.validators.length; i++) {
// If v is set to 0, this signifies that it was not possible to get a signature from this validator and we skip evaluation
// (In a valid signature, it is either 27 or 28)
if (_sigs[i].v != 0) {
// Check that the current validator has signed off on the hash
if (!verifySig(_currentValset.validators[i], _theHash, _sigs[i])) {
revert InvalidSignature();
}
// Sum up cumulative power
cumulativePower = cumulativePower + _currentValset.powers[i];
// Break early to avoid wasting gas
if (cumulativePower > _powerThreshold) {
break;
}
}
}
// Check that there was enough power
if (cumulativePower <= _powerThreshold) {
revert InsufficientPower(cumulativePower, _powerThreshold);
}
// Success
}
// This updates the valset by checking that the validators in the current valset have signed off on the
// new valset. The signatures supplied are the signatures of the current valset over the checkpoint hash
// generated from the new valset.
// Anyone can call this function, but they must supply valid signatures of constant_powerThreshold of the current valset over
// the new valset.
function updateValset(
// The new version of the validator set
ValsetArgs calldata _newValset,
// The current validators that approve the change
ValsetArgs calldata _currentValset,
// These are arrays of the parts of the current validator's signatures
Signature[] calldata _sigs
) external {
// CHECKS
// Check that the valset nonce is greater than the old one
if (_newValset.valsetNonce <= _currentValset.valsetNonce) {
revert InvalidValsetNonce({
newNonce: _newValset.valsetNonce,
currentNonce: _currentValset.valsetNonce
});
}
// Check that the valset nonce is less than a million nonces forward from the old one
// this makes it difficult for an attacker to lock out the contract by getting a single
// bad validator set through with uint256 max nonce
if (_newValset.valsetNonce > _currentValset.valsetNonce + 1000000) {
revert InvalidValsetNonce({
newNonce: _newValset.valsetNonce,
currentNonce: _currentValset.valsetNonce
});
}
// Check that new validators and powers set is well-formed
if (
_newValset.validators.length != _newValset.powers.length ||
_newValset.validators.length == 0
) {
revert MalformedNewValidatorSet();
}
// Check that current validators, powers, and signatures (v,r,s) set is well-formed
validateValset(_currentValset, _sigs);
// Check cumulative power to ensure the contract has sufficient power to actually
// pass a vote
uint256 cumulativePower = 0;
for (uint256 i = 0; i < _newValset.powers.length; i++) {
cumulativePower = cumulativePower + _newValset.powers[i];
if (cumulativePower > constant_powerThreshold) {
break;
}
}
if (cumulativePower <= constant_powerThreshold) {
revert InsufficientPower({
cumulativePower: cumulativePower,
powerThreshold: constant_powerThreshold
});
}
// Check that the supplied current validator set matches the saved checkpoint
if (makeCheckpoint(_currentValset, state_gravityId) != state_lastValsetCheckpoint) {
revert IncorrectCheckpoint();
}
// Check that enough current validators have signed off on the new validator set
bytes32 newCheckpoint = makeCheckpoint(_newValset, state_gravityId);
checkValidatorSignatures(_currentValset, _sigs, newCheckpoint, constant_powerThreshold);
// ACTIONS
// Stored to be used next time to validate that the valset
// supplied by the caller is correct.
state_lastValsetCheckpoint = newCheckpoint;
// Store new nonce
state_lastValsetNonce = _newValset.valsetNonce;
// Send submission reward to msg.sender if reward token is a valid value
if (_newValset.rewardToken != address(0) && _newValset.rewardAmount != 0) {
IERC20(_newValset.rewardToken).safeTransfer(msg.sender, _newValset.rewardAmount);
}
// LOGS
state_lastEventNonce = state_lastEventNonce + 1;
emit ValsetUpdatedEvent(
_newValset.valsetNonce,
state_lastEventNonce,
_newValset.rewardAmount,
_newValset.rewardToken,
_newValset.validators,
_newValset.powers
);
}
// submitBatch processes a batch of Cosmos -> Ethereum transactions by sending the tokens in the transactions
// to the destination addresses. It is approved by the current Cosmos validator set.
// Anyone can call this function, but they must supply valid signatures of constant_powerThreshold of the current valset over
// the batch.
function submitBatch(
// The validators that approve the batch
ValsetArgs calldata _currentValset,
// These are arrays of the parts of the validators signatures
Signature[] calldata _sigs,
// The batch of transactions
uint256[] calldata _amounts,
address[] calldata _destinations,
uint256[] calldata _fees,
uint256 _batchNonce,
address _tokenContract,
// a block height beyond which this batch is not valid
// used to provide a fee-free timeout
uint256 _batchTimeout
) external nonReentrant {
// CHECKS scoped to reduce stack depth
{
// Check that the batch nonce is higher than the last nonce for this token
if (_batchNonce <= state_lastBatchNonces[_tokenContract]) {
revert InvalidBatchNonce({
newNonce: _batchNonce,
currentNonce: state_lastBatchNonces[_tokenContract]
});
}
// Check that the batch nonce is less than one million nonces forward from the old one
// this makes it difficult for an attacker to lock out the contract by getting a single
// bad batch through with uint256 max nonce
if (_batchNonce > state_lastBatchNonces[_tokenContract] + 1000000) {
revert InvalidBatchNonce({
newNonce: _batchNonce,
currentNonce: state_lastBatchNonces[_tokenContract]
});
}
// Check that the block height is less than the timeout height
if (block.number >= _batchTimeout) {
revert BatchTimedOut();
}
// Check that current validators, powers, and signatures (v,r,s) set is well-formed
validateValset(_currentValset, _sigs);
// Check that the supplied current validator set matches the saved checkpoint
if (makeCheckpoint(_currentValset, state_gravityId) != state_lastValsetCheckpoint) {
revert IncorrectCheckpoint();
}
// Check that the transaction batch is well-formed
if (_amounts.length != _destinations.length || _amounts.length != _fees.length) {
revert MalformedBatch();
}
// Check that enough current validators have signed off on the transaction batch and valset
checkValidatorSignatures(
_currentValset,
_sigs,
// Get hash of the transaction batch and checkpoint
keccak256(
abi.encode(
state_gravityId,
// bytes32 encoding of "transactionBatch"
0x7472616e73616374696f6e426174636800000000000000000000000000000000,
_amounts,
_destinations,
_fees,
_batchNonce,
_tokenContract,
_batchTimeout
)
),
constant_powerThreshold
);
// ACTIONS
// Store batch nonce
state_lastBatchNonces[_tokenContract] = _batchNonce;
{
// Send transaction amounts to destinations
uint256 totalFee;
for (uint256 i = 0; i < _amounts.length; i++) {
IERC20(_tokenContract).safeTransfer(_destinations[i], _amounts[i]);
totalFee = totalFee + _fees[i];
}
// Send transaction fees to msg.sender
IERC20(_tokenContract).safeTransfer(msg.sender, totalFee);
}
}
// LOGS scoped to reduce stack depth
{
state_lastEventNonce = state_lastEventNonce + 1;
emit TransactionBatchExecutedEvent(_batchNonce, _tokenContract, state_lastEventNonce);
}
}
// This makes calls to contracts that execute arbitrary logic
// First, it gives the logic contract some tokens
// Then, it gives msg.senders tokens for fees
// Then, it calls an arbitrary function on the logic contract
// invalidationId and invalidationNonce are used for replay prevention.
// They can be used to implement a per-token nonce by setting the token
// address as the invalidationId and incrementing the nonce each call.
// They can be used for nonce-free replay prevention by using a different invalidationId
// for each call.
function submitLogicCall(
// The validators that approve the call
ValsetArgs calldata _currentValset,
// These are arrays of the parts of the validators signatures
Signature[] calldata _sigs,
LogicCallArgs memory _args
) external nonReentrant {
// CHECKS scoped to reduce stack depth
{
// Check that the call has not timed out
if (block.number >= _args.timeOut) {
revert LogicCallTimedOut();
}
// Check that the invalidation nonce is higher than the last nonce for this invalidation Id
if (state_invalidationMapping[_args.invalidationId] >= _args.invalidationNonce) {
revert InvalidLogicCallNonce({
newNonce: _args.invalidationNonce,
currentNonce: state_invalidationMapping[_args.invalidationId]
});
}
// note the lack of nonce skipping check, it's not needed here since an attacker
// will never be able to fill the invalidationId space, therefore a nonce lockout
// is simply not possible
// Check that current validators, powers, and signatures (v,r,s) set is well-formed
validateValset(_currentValset, _sigs);
// Check that the supplied current validator set matches the saved checkpoint
if (makeCheckpoint(_currentValset, state_gravityId) != state_lastValsetCheckpoint) {
revert IncorrectCheckpoint();
}
if (_args.transferAmounts.length != _args.transferTokenContracts.length) {
revert InvalidLogicCallTransfers();
}
if (_args.feeAmounts.length != _args.feeTokenContracts.length) {
revert InvalidLogicCallFees();
}
}
{
bytes32 argsHash = keccak256(
abi.encode(
state_gravityId,
// bytes32 encoding of "logicCall"
0x6c6f67696343616c6c0000000000000000000000000000000000000000000000,
_args.transferAmounts,
_args.transferTokenContracts,
_args.feeAmounts,
_args.feeTokenContracts,
_args.logicContractAddress,
_args.payload,
_args.timeOut,
_args.invalidationId,
_args.invalidationNonce
)
);
// Check that enough current validators have signed off on the transaction batch and valset
checkValidatorSignatures(
_currentValset,
_sigs,
// Get hash of the transaction batch and checkpoint
argsHash,
constant_powerThreshold
);
}
// ACTIONS
// Update invaldiation nonce
state_invalidationMapping[_args.invalidationId] = _args.invalidationNonce;
// Send tokens to the logic contract
for (uint256 i = 0; i < _args.transferAmounts.length; i++) {
IERC20(_args.transferTokenContracts[i]).safeTransfer(
_args.logicContractAddress,
_args.transferAmounts[i]
);
}
// Make call to logic contract
bytes memory returnData = Address.functionCall(_args.logicContractAddress, _args.payload);
// Send fees to msg.sender
for (uint256 i = 0; i < _args.feeAmounts.length; i++) {
IERC20(_args.feeTokenContracts[i]).safeTransfer(msg.sender, _args.feeAmounts[i]);
}
// LOGS scoped to reduce stack depth
{
state_lastEventNonce = state_lastEventNonce + 1;
emit LogicCallEvent(
_args.invalidationId,
_args.invalidationNonce,
returnData,
state_lastEventNonce
);
}
}
function sendToCosmos(
address _tokenContract,
string calldata _destination,
uint256 _amount
) external nonReentrant {
// we snapshot our current balance of this token
uint256 ourStartingBalance = IERC20(_tokenContract).balanceOf(address(this));
// attempt to transfer the user specified amount
IERC20(_tokenContract).safeTransferFrom(msg.sender, address(this), _amount);
// check what this particular ERC20 implementation actually gave us, since it doesn't
// have to be at all related to the _amount
uint256 ourEndingBalance = IERC20(_tokenContract).balanceOf(address(this));
// a very strange ERC20 may trigger this condition, if we didn't have this we would
// underflow, so it's mostly just an error message printer
if (ourEndingBalance <= ourStartingBalance) {
revert InvalidSendToCosmos();
}
state_lastEventNonce = state_lastEventNonce + 1;
// emit to Cosmos the actual amount our balance has changed, rather than the user
// provided amount. This protects against a small set of wonky ERC20 behavior, like
// burning on send but not tokens that for example change every users balance every day.
emit SendToCosmosEvent(
_tokenContract,
msg.sender,
_destination,
ourEndingBalance - ourStartingBalance,
state_lastEventNonce
);
}
function deployERC20(
string calldata _cosmosDenom,
string calldata _name,
string calldata _symbol,
uint8 _decimals
) external {
// NOTE this is an edit made for the nym codebase unit tests - in the canonical bridge it is the entire token supply
// Deploy an ERC20 with half of entire supply granted to Gravity.sol
TestCosmosERC20 erc20 = new TestCosmosERC20(address(this), _name, _symbol, _decimals);
// Fire an event to let the Cosmos module know
state_lastEventNonce = state_lastEventNonce + 1;
emit ERC20DeployedEvent(
_cosmosDenom,
address(erc20),
_name,
_symbol,
_decimals,
state_lastEventNonce
);
}
constructor(
// A unique identifier for this gravity instance to use in signatures
bytes32 _gravityId,
// The validator set, not in valset args format since many of it's
// arguments would never be used in this case
address[] memory _validators,
uint256[] memory _powers
) {
// CHECKS
// Check that validators, powers, and signatures (v,r,s) set is well-formed
if (_validators.length != _powers.length || _validators.length == 0) {
revert MalformedCurrentValidatorSet();
}
// Check cumulative power to ensure the contract has sufficient power to actually
// pass a vote
uint256 cumulativePower = 0;
for (uint256 i = 0; i < _powers.length; i++) {
cumulativePower = cumulativePower + _powers[i];
if (cumulativePower > constant_powerThreshold) {
break;
}
}
if (cumulativePower <= constant_powerThreshold) {
revert InsufficientPower({
cumulativePower: cumulativePower,
powerThreshold: constant_powerThreshold
});
}
ValsetArgs memory _valset;
_valset = ValsetArgs(_validators, _powers, 0, 0, address(0));
bytes32 newCheckpoint = makeCheckpoint(_valset, _gravityId);
// ACTIONS
state_gravityId = _gravityId;
state_lastValsetCheckpoint = newCheckpoint;
// LOGS
emit ValsetUpdatedEvent(
state_lastValsetNonce,
state_lastEventNonce,
0,
address(0),
_validators,
_powers
);
}
}
@@ -1,43 +0,0 @@
require("@nomiclabs/hardhat-etherscan");
require("@nomiclabs/hardhat-truffle5");
require("@nomiclabs/hardhat-web3");
require("@nomiclabs/hardhat-ethers");
require('dotenv').config({ path: require('find-config')('.env') });
/**
* @type import('hardhat/config').HardhatUserConfig
*/
module.exports = {
solidity: {
version: "0.8.10",
settings: {
optimizer: {
enabled: true
}
} },
// paths: {
// artifacts: "./artifacts/contracts"
// },
networks: {
localhost: {
url: "http://127.0.0.1:8545"
},
rinkeby: {
url: process.env.RINKEBY_URL, //Infura url with projectId
accounts: [process.env.PRIV_KEY], // private key of account used for contract interaction
gas: "auto",
gasPrice: "auto"
},
mainnet: {
url: process.env.MAINNET_URL, //Infura url with projectId
accounts: [process.env.PRIV_KEY], // private key of account used for contract interaction
gas: "auto",
gasPrice: "auto"
}
},
etherscan: {
// Your API key for Etherscan
// Obtain one at https://etherscan.io/
apiKey: process.env.ETHERSCAN_API_KEY
}
};
+7 -7
View File
@@ -7,7 +7,7 @@
"dependencies": {
"@nomiclabs/hardhat-truffle5": "^2.0.5",
"@nomiclabs/hardhat-web3": "^2.0.0",
"@openzeppelin/contracts": "4.5.0",
"@openzeppelin/contracts": "4.7.3",
"@openzeppelin/test-helpers": "^0.5.15",
"dotenv": "^16.0.0",
"file-system": "^2.2.2",
@@ -1920,9 +1920,9 @@
}
},
"node_modules/@openzeppelin/contracts": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.5.0.tgz",
"integrity": "sha512-fdkzKPYMjrRiPK6K4y64e6GzULR7R7RwxSigHS8DDp7aWDeoReqsQI+cxHV1UuhAqX69L1lAaWDxenfP+xiqzA=="
"version": "4.7.3",
"resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.7.3.tgz",
"integrity": "sha512-dGRS0agJzu8ybo44pCIf3xBaPQN/65AIXNgK8+4gzKd5kbvlqyxryUYVLJv7fK98Seyd2hDZzVEHSWAh0Bt1Yw=="
},
"node_modules/@openzeppelin/test-helpers": {
"version": "0.5.15",
@@ -21190,9 +21190,9 @@
}
},
"@openzeppelin/contracts": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.5.0.tgz",
"integrity": "sha512-fdkzKPYMjrRiPK6K4y64e6GzULR7R7RwxSigHS8DDp7aWDeoReqsQI+cxHV1UuhAqX69L1lAaWDxenfP+xiqzA=="
"version": "4.7.3",
"resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.7.3.tgz",
"integrity": "sha512-dGRS0agJzu8ybo44pCIf3xBaPQN/65AIXNgK8+4gzKd5kbvlqyxryUYVLJv7fK98Seyd2hDZzVEHSWAh0Bt1Yw=="
},
"@openzeppelin/test-helpers": {
"version": "0.5.15",
@@ -12,7 +12,7 @@
"dependencies": {
"@nomiclabs/hardhat-truffle5": "^2.0.5",
"@nomiclabs/hardhat-web3": "^2.0.0",
"@openzeppelin/contracts": "4.5.0",
"@openzeppelin/contracts": "4.7.3",
"@openzeppelin/test-helpers": "^0.5.15",
"dotenv": "^16.0.0",
"file-system": "^2.2.2",
@@ -1,34 +0,0 @@
const { ethers } = require('hardhat');
const { constants } = require('@openzeppelin/test-helpers');
const contracts = require("../../contractAddresses.json");
const fs = require('file-system');
async function main() {
const BandwidthGenerator = await ethers.getContractFactory("BandwidthGenerator");
console.log('preparing to deploy contract...')
// if this is failing, check whether the ERC20 address has been manually added to the contract addresses json file
const bandwidthGenerator = await BandwidthGenerator.deploy(
contracts.mainnet.NYM_ERC20,
contracts.mainnet.GRAVITY
);
console.log('...contract successfully deployed...');
contracts.mainnet.BANDWIDTH_GENERATOR = bandwidthGenerator.address;
// the location of the json file is relative to where you are running the script from - run from root of directory
fs.writeFileSync('./contractAddresses.json', JSON.stringify(contracts), (err) => {
if (err) throw err;
});
console.log(`...bandwidthGenerator.sol deployed at ${bandwidthGenerator.address}`);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
@@ -1,6 +0,0 @@
// arguments for verification of bandwidth generator constructed with NYMT via hardhat-etherscan plugin
// npx hardhat verify --constructor-args ./scripts/mainnet/mainnet-nymt-bandwidth-args.js --network mainnet 0xB3BF30DD53044c9050B7309031Bbf26b2cecF3be
module.exports = [
"0xE8883BAeF3869e14E4823F46662e81D4F7d2A81F",
"0xa4108aA1Ec4967F8b52220a4f7e94A8201F2D906"
];
@@ -1,8 +0,0 @@
// arguments for verification of gravity contract via hardhat-etherscan plugin
// npx hardhat verify --constructor-args ./scripts/mainnet/mainnet-token-args.js --network mainnet 0xCf6DeE9947fdDc958985E5283e63d41CBC83Ff61
module.exports = [
"0xa4108aA1Ec4967F8b52220a4f7e94A8201F2D906",
"nym",
"nym",
6
];

Some files were not shown because too many files have changed in this diff Show More