Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dd83631be1 | |||
| 405ca63c7b | |||
| 7f17522666 | |||
| ba158f822d | |||
| 0f147750ae | |||
| 60d17389d0 | |||
| 94d70e47fc | |||
| ecbf5296a5 | |||
| f3454409f8 | |||
| 7d2e90b69f | |||
| bb2732bcc6 | |||
| d196993993 | |||
| 065fe812ae | |||
| 2aa18fb77c | |||
| 8a0e7fb9d6 | |||
| 13c2ca4a78 | |||
| 6af84535fa | |||
| c8699cbe8d | |||
| 974163da97 | |||
| 7290e479db | |||
| c7b728318c | |||
| 5c6d31bcb5 | |||
| 3823292ba8 | |||
| 3eeda4a421 | |||
| d039c25b55 | |||
| 16ef1c547b | |||
| e804b014a8 | |||
| e406a05694 | |||
| 057b3456a7 | |||
| 5ac124e159 | |||
| f29c6a0550 | |||
| 242a6d13af | |||
| 7816b4c839 | |||
| e7ed48e55e | |||
| 63855f6ca4 | |||
| 2f53e40355 | |||
| bb9753cda6 | |||
| 95d0afdeb6 | |||
| fbe02fa7fb | |||
| 46e206e8f0 | |||
| 55bdcecffb | |||
| 2ee4b8fec6 | |||
| 67900956f8 | |||
| 29166c1d6a | |||
| db24170752 | |||
| fc90d5a389 | |||
| af1a83fe83 | |||
| e12b69e58f | |||
| d38614b15c | |||
| 627d12239e | |||
| 1af1370f23 | |||
| 124103d51b |
@@ -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',
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
+2
-1
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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 }
|
||||
};
|
||||
|
||||
@@ -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(ð_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(ð_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?)
|
||||
}
|
||||
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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"]}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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],
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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" }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -8,5 +8,4 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
nymsphinx-addressing = { path = "../../../common/nymsphinx/addressing" }
|
||||
ordered-buffer = {path = "../ordered-buffer"}
|
||||
thiserror = "1"
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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",
|
||||
] }
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
## Unreleased
|
||||
## [nym-contracts-v1.0.2](https://github.com/nymtech/nym/tree/nym-contracts-v1.0.2) (2022-09-13)
|
||||
|
||||
### Added
|
||||
|
||||
|
||||
Generated
+325
-340
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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"
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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()]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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`
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/1f95016aa87f8376998977fedfcae017.json"
|
||||
}
|
||||
-297
File diff suppressed because one or more lines are too long
-4
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/1f95016aa87f8376998977fedfcae017.json"
|
||||
}
|
||||
-194
@@ -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": {}
|
||||
}
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../../build-info/1f95016aa87f8376998977fedfcae017.json"
|
||||
}
|
||||
-233
@@ -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": {}
|
||||
}
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/1f95016aa87f8376998977fedfcae017.json"
|
||||
}
|
||||
-10
@@ -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
@@ -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
Reference in New Issue
Block a user