Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1486450b6e | |||
| a79107cd9a | |||
| ecbf5296a5 | |||
| f3454409f8 | |||
| 7d2e90b69f | |||
| bb2732bcc6 | |||
| d196993993 | |||
| 065fe812ae | |||
| 2aa18fb77c | |||
| 8a0e7fb9d6 | |||
| 13c2ca4a78 | |||
| 6af84535fa | |||
| c8699cbe8d | |||
| 974163da97 | |||
| 7290e479db | |||
| c7b728318c | |||
| 5c6d31bcb5 | |||
| 3823292ba8 | |||
| 3eeda4a421 | |||
| d039c25b55 | |||
| 16ef1c547b | |||
| e804b014a8 | |||
| e406a05694 | |||
| 057b3456a7 | |||
| 5ac124e159 | |||
| 7816b4c839 | |||
| e7ed48e55e | |||
| 63855f6ca4 | |||
| 1af1370f23 |
@@ -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,27 +0,0 @@
|
||||
name: Notification 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_NYMTECH_TEAM }}"
|
||||
KEYBASE_NYM_CHANNEL: "test"
|
||||
WORKFLOW_CONCLUSION: "failure"
|
||||
uses: docker://keybaseio/client:stable-node
|
||||
with:
|
||||
args: .github/workflows/support-files/notifications/entry_point.sh
|
||||
@@ -14,10 +14,12 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
- 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
+178
-224
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -85,4 +85,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
|
||||
|
||||
+2
-2
@@ -37,7 +37,7 @@ impl AcknowledgementListener {
|
||||
}
|
||||
|
||||
async fn on_ack(&mut self, ack_content: Vec<u8>) {
|
||||
debug!("Received an ack");
|
||||
trace!("Received an ack");
|
||||
let frag_id = match recover_identifier(&self.ack_key, &ack_content)
|
||||
.map(FragmentIdentifier::try_from_bytes)
|
||||
{
|
||||
@@ -60,7 +60,7 @@ impl AcknowledgementListener {
|
||||
return;
|
||||
}
|
||||
|
||||
trace!("Received {} from the mix network", frag_id);
|
||||
debug!("Received {} from the mix network", frag_id);
|
||||
|
||||
self.action_sender
|
||||
.unbounded_send(Action::new_remove(frag_id))
|
||||
|
||||
+5
-5
@@ -128,7 +128,7 @@ impl ActionController {
|
||||
fn handle_insert(&mut self, pending_acks: Vec<PendingAcknowledgement>) {
|
||||
for pending_ack in pending_acks {
|
||||
let frag_id = pending_ack.message_chunk.fragment_identifier();
|
||||
trace!("{} is inserted", frag_id);
|
||||
debug!("{} is inserted", frag_id);
|
||||
|
||||
if self
|
||||
.pending_acks_data
|
||||
@@ -141,7 +141,7 @@ impl ActionController {
|
||||
}
|
||||
|
||||
fn handle_start_timer(&mut self, frag_id: FragmentIdentifier) {
|
||||
trace!("{} is starting its timer", frag_id);
|
||||
debug!("{} is starting its timer", frag_id);
|
||||
|
||||
if let Some((pending_ack_data, queue_key)) = self.pending_acks_data.get_mut(&frag_id) {
|
||||
if queue_key.is_some() {
|
||||
@@ -164,7 +164,7 @@ impl ActionController {
|
||||
}
|
||||
|
||||
fn handle_remove(&mut self, frag_id: FragmentIdentifier) {
|
||||
trace!("{} is getting removed", frag_id);
|
||||
debug!("{} is getting removed", frag_id);
|
||||
|
||||
match self.pending_acks_data.remove(&frag_id) {
|
||||
None => {
|
||||
@@ -195,7 +195,7 @@ impl ActionController {
|
||||
// initiated basically as a first step of retransmission. At first data has its delay updated
|
||||
// (as new sphinx packet was created with new expected delivery time)
|
||||
fn handle_update_delay(&mut self, frag_id: FragmentIdentifier, delay: SphinxDelay) {
|
||||
trace!("{} is updating its delay", frag_id);
|
||||
debug!("{} is updating its delay", frag_id);
|
||||
// TODO: is it possible to solve this without either locking or temporarily removing the value?
|
||||
if let Some((pending_ack_data, queue_key)) = self.pending_acks_data.remove(&frag_id) {
|
||||
// this Action is triggered by `RetransmissionRequestListener` which held the other potential
|
||||
@@ -221,7 +221,7 @@ impl ActionController {
|
||||
// about it. Perhaps just reschedule it at later point?
|
||||
let frag_id = expired_ack.into_inner();
|
||||
|
||||
trace!("{} has expired", frag_id);
|
||||
debug!("{} has expired", frag_id);
|
||||
|
||||
if let Some((pending_ack_data, queue_key)) = self.pending_acks_data.get_mut(&frag_id) {
|
||||
if queue_key.is_none() {
|
||||
|
||||
+2
@@ -104,6 +104,7 @@ where
|
||||
content: Vec<u8>,
|
||||
with_reply_surb: bool,
|
||||
) -> Option<Vec<RealMessage>> {
|
||||
log::debug!("handle_fresh_message");
|
||||
let topology_permit = self.topology_access.get_read_permit().await;
|
||||
let topology = match topology_permit
|
||||
.try_get_valid_topology_ref(&self.ack_recipient, Some(&recipient))
|
||||
@@ -160,6 +161,7 @@ where
|
||||
}
|
||||
|
||||
async fn on_input_message(&mut self, msg: InputMessage) {
|
||||
log::debug!("on_input_message");
|
||||
let real_messages = match msg {
|
||||
InputMessage::Fresh {
|
||||
recipient,
|
||||
|
||||
@@ -178,6 +178,9 @@ where
|
||||
config.average_ack_delay,
|
||||
);
|
||||
|
||||
let message_preparer =
|
||||
message_preparer.with_packet_size(nymsphinx::params::PacketSize::ExtendedPacket);
|
||||
|
||||
// will listen for any acks coming from the network
|
||||
let acknowledgement_listener = AcknowledgementListener::new(
|
||||
Arc::clone(&ack_key),
|
||||
|
||||
@@ -50,9 +50,15 @@ pub struct Config {
|
||||
|
||||
/// Average delay an acknowledgement packet is going to get delayed at a single mixnode.
|
||||
average_ack_delay_duration: Duration,
|
||||
|
||||
/// Controls whether the main packet stream constantly produces packets according to the predefined
|
||||
/// poisson distribution.
|
||||
disable_main_poisson_packet_distribution: bool,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
// TODO: change the config into a builder
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
ack_key: Arc<AckKey>,
|
||||
ack_wait_multiplier: f64,
|
||||
@@ -60,6 +66,7 @@ impl Config {
|
||||
average_ack_delay_duration: Duration,
|
||||
average_message_sending_delay: Duration,
|
||||
average_packet_delay_duration: Duration,
|
||||
disable_main_poisson_packet_distribution: bool,
|
||||
self_recipient: Recipient,
|
||||
) -> Self {
|
||||
Config {
|
||||
@@ -70,6 +77,7 @@ impl Config {
|
||||
average_message_sending_delay,
|
||||
average_packet_delay_duration,
|
||||
average_ack_delay_duration,
|
||||
disable_main_poisson_packet_distribution,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,6 +136,7 @@ impl RealMessagesController<OsRng> {
|
||||
config.average_ack_delay_duration,
|
||||
config.average_packet_delay_duration,
|
||||
config.average_message_sending_delay,
|
||||
config.disable_main_poisson_packet_distribution,
|
||||
);
|
||||
|
||||
let out_queue_control = OutQueueControl::new(
|
||||
|
||||
@@ -32,6 +32,10 @@ pub(crate) struct Config {
|
||||
|
||||
/// Average delay between sending subsequent packets.
|
||||
average_message_sending_delay: Duration,
|
||||
|
||||
/// Controls whether the stream constantly produces packets according to the predefined
|
||||
/// poisson distribution.
|
||||
disable_poisson_packet_distribution: bool,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
@@ -39,11 +43,13 @@ impl Config {
|
||||
average_ack_delay: Duration,
|
||||
average_packet_delay: Duration,
|
||||
average_message_sending_delay: Duration,
|
||||
disable_poisson_packet_distribution: bool,
|
||||
) -> Self {
|
||||
Config {
|
||||
average_ack_delay,
|
||||
average_packet_delay,
|
||||
average_message_sending_delay,
|
||||
disable_poisson_packet_distribution,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,7 +69,7 @@ where
|
||||
|
||||
/// Internal state, determined by `average_message_sending_delay`,
|
||||
/// used to keep track of when a next packet should be sent out.
|
||||
next_delay: Pin<Box<time::Sleep>>,
|
||||
next_delay: Option<Pin<Box<time::Sleep>>>,
|
||||
|
||||
/// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them
|
||||
/// out to the network without any further delays.
|
||||
@@ -113,55 +119,6 @@ pub(crate) enum StreamMessage {
|
||||
Real(Box<RealMessage>),
|
||||
}
|
||||
|
||||
impl<R> Stream for OutQueueControl<R>
|
||||
where
|
||||
R: CryptoRng + Rng + Unpin,
|
||||
{
|
||||
type Item = StreamMessage;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
// it is not yet time to return a message
|
||||
if self.next_delay.as_mut().poll(cx).is_pending() {
|
||||
return Poll::Pending;
|
||||
};
|
||||
|
||||
// we know it's time to send a message, so let's prepare delay for the next one
|
||||
// Get the `now` by looking at the current `delay` deadline
|
||||
let avg_delay = self.config.average_message_sending_delay;
|
||||
let now = self.next_delay.deadline();
|
||||
let next_poisson_delay = sample_poisson_duration(&mut self.rng, avg_delay);
|
||||
|
||||
// The next interval value is `next_poisson_delay` after the one that just
|
||||
// yielded.
|
||||
let next = now + next_poisson_delay;
|
||||
self.next_delay.as_mut().reset(next);
|
||||
|
||||
// check if we have anything immediately available
|
||||
if let Some(real_available) = self.received_buffer.pop_front() {
|
||||
return Poll::Ready(Some(StreamMessage::Real(Box::new(real_available))));
|
||||
}
|
||||
|
||||
// decide what kind of message to send
|
||||
match Pin::new(&mut self.real_receiver).poll_next(cx) {
|
||||
// in the case our real message channel stream was closed, we should also indicate we are closed
|
||||
// (and whoever is using the stream should panic)
|
||||
Poll::Ready(None) => Poll::Ready(None),
|
||||
|
||||
// if there are more messages available, return first one and store the rest
|
||||
Poll::Ready(Some(real_messages)) => {
|
||||
self.received_buffer = real_messages.into();
|
||||
// we MUST HAVE received at least ONE message
|
||||
Poll::Ready(Some(StreamMessage::Real(Box::new(
|
||||
self.received_buffer.pop_front().unwrap(),
|
||||
))))
|
||||
}
|
||||
|
||||
// otherwise construct a dummy one
|
||||
Poll::Pending => Poll::Ready(Some(StreamMessage::Cover)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> OutQueueControl<R>
|
||||
where
|
||||
R: CryptoRng + Rng + Unpin,
|
||||
@@ -184,7 +141,7 @@ where
|
||||
config,
|
||||
ack_key,
|
||||
sent_notifier,
|
||||
next_delay: Box::pin(time::sleep(Default::default())),
|
||||
next_delay: None,
|
||||
mix_tx,
|
||||
real_receiver,
|
||||
our_full_destination,
|
||||
@@ -265,12 +222,6 @@ where
|
||||
|
||||
// Send messages at certain rate and if no real traffic is available, send cover message.
|
||||
async fn run_normal_out_queue(&mut self) {
|
||||
// we should set initial delay only when we actually start the stream
|
||||
self.next_delay = Box::pin(time::sleep(sample_poisson_duration(
|
||||
&mut self.rng,
|
||||
self.config.average_message_sending_delay,
|
||||
)));
|
||||
|
||||
let mut shutdown = self.shutdown.clone();
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
@@ -297,4 +248,110 @@ where
|
||||
debug!("Starting out queue controller...");
|
||||
self.run_normal_out_queue().await
|
||||
}
|
||||
|
||||
fn poll_poisson(&mut self, cx: &mut Context<'_>) -> Poll<Option<StreamMessage>> {
|
||||
if let Some(ref mut next_delay) = &mut self.next_delay {
|
||||
// it is not yet time to return a message
|
||||
if next_delay.as_mut().poll(cx).is_pending() {
|
||||
return Poll::Pending;
|
||||
};
|
||||
|
||||
// we know it's time to send a message, so let's prepare delay for the next one
|
||||
// Get the `now` by looking at the current `delay` deadline
|
||||
let avg_delay = self.config.average_message_sending_delay;
|
||||
let now = next_delay.deadline();
|
||||
let next_poisson_delay = sample_poisson_duration(&mut self.rng, avg_delay);
|
||||
|
||||
// The next interval value is `next_poisson_delay` after the one that just
|
||||
// yielded.
|
||||
let next = now + next_poisson_delay;
|
||||
next_delay.as_mut().reset(next);
|
||||
|
||||
// check if we have anything immediately available
|
||||
if let Some(real_available) = self.received_buffer.pop_front() {
|
||||
return Poll::Ready(Some(StreamMessage::Real(Box::new(real_available))));
|
||||
}
|
||||
|
||||
// decide what kind of message to send
|
||||
match Pin::new(&mut self.real_receiver).poll_next(cx) {
|
||||
// in the case our real message channel stream was closed, we should also indicate we are closed
|
||||
// (and whoever is using the stream should panic)
|
||||
Poll::Ready(None) => Poll::Ready(None),
|
||||
|
||||
// if there are more messages available, return first one and store the rest
|
||||
Poll::Ready(Some(real_messages)) => {
|
||||
self.received_buffer = real_messages.into();
|
||||
// we MUST HAVE received at least ONE message
|
||||
Poll::Ready(Some(StreamMessage::Real(Box::new(
|
||||
self.received_buffer.pop_front().unwrap(),
|
||||
))))
|
||||
}
|
||||
|
||||
// otherwise construct a dummy one
|
||||
Poll::Pending => Poll::Ready(Some(StreamMessage::Cover)),
|
||||
}
|
||||
} else {
|
||||
// we never set an initial delay - let's do it now
|
||||
cx.waker().wake_by_ref();
|
||||
|
||||
self.next_delay = Some(Box::pin(time::sleep(sample_poisson_duration(
|
||||
&mut self.rng,
|
||||
self.config.average_message_sending_delay,
|
||||
))));
|
||||
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_immediate(&mut self, cx: &mut Context<'_>) -> Poll<Option<StreamMessage>> {
|
||||
// check if we have anything immediately available
|
||||
if let Some(real_available) = self.received_buffer.pop_front() {
|
||||
// if there are more messages immediately available, notify the runtime
|
||||
// because we should be polled again
|
||||
if !self.received_buffer.is_empty() {
|
||||
cx.waker().wake_by_ref()
|
||||
}
|
||||
return Poll::Ready(Some(StreamMessage::Real(Box::new(real_available))));
|
||||
}
|
||||
|
||||
match Pin::new(&mut self.real_receiver).poll_next(cx) {
|
||||
// in the case our real message channel stream was closed, we should also indicate we are closed
|
||||
// (and whoever is using the stream should panic)
|
||||
Poll::Ready(None) => Poll::Ready(None),
|
||||
|
||||
// if there are more messages available, return first one and store the rest
|
||||
Poll::Ready(Some(real_messages)) => {
|
||||
self.received_buffer = real_messages.into();
|
||||
// we MUST HAVE received at least ONE message
|
||||
Poll::Ready(Some(StreamMessage::Real(Box::new(
|
||||
self.received_buffer.pop_front().unwrap(),
|
||||
))))
|
||||
}
|
||||
|
||||
// if there's nothing, then there's nothing
|
||||
Poll::Pending => Poll::Pending,
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_next_message(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<StreamMessage>> {
|
||||
if self.config.disable_poisson_packet_distribution {
|
||||
self.poll_immediate(cx)
|
||||
} else {
|
||||
self.poll_poisson(cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Stream for OutQueueControl<R>
|
||||
where
|
||||
R: CryptoRng + Rng + Unpin,
|
||||
{
|
||||
type Item = StreamMessage;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
self.poll_next_message(cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +73,11 @@ impl ReceivedMessagesBufferInner {
|
||||
if self.recently_reconstructed.contains(&fragment.id()) {
|
||||
debug!("Received a chunk of already re-assembled message ({:?})! It probably got here because the ack got lost", fragment.id());
|
||||
return None;
|
||||
} else {
|
||||
debug!(
|
||||
"Received a chunk of message ({:?}) for the first time",
|
||||
fragment.id()
|
||||
);
|
||||
}
|
||||
|
||||
// if we returned an error the underlying message is malformed in some way
|
||||
@@ -204,7 +209,7 @@ impl ReceivedMessagesBuffer {
|
||||
}
|
||||
|
||||
async fn handle_new_received(&mut self, msgs: Vec<Vec<u8>>) {
|
||||
debug!(
|
||||
trace!(
|
||||
"Processing {:?} new message that might get added to the buffer!",
|
||||
msgs.len()
|
||||
);
|
||||
|
||||
@@ -14,8 +14,10 @@ pub const MISSING_VALUE: &str = "MISSING VALUE";
|
||||
|
||||
// 'DEBUG'
|
||||
const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 1.5;
|
||||
//const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 10.0;
|
||||
|
||||
const DEFAULT_ACK_WAIT_ADDITION: Duration = Duration::from_millis(1_500);
|
||||
//const DEFAULT_ACK_WAIT_ADDITION: Duration = Duration::from_millis(1_500);
|
||||
const DEFAULT_ACK_WAIT_ADDITION: Duration = Duration::from_millis(10_000);
|
||||
const DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(200);
|
||||
const DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(20);
|
||||
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(50);
|
||||
@@ -121,16 +123,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 +201,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
|
||||
@@ -256,6 +238,10 @@ impl<T: NymConfig> Config<T> {
|
||||
self.debug.topology_resolution_timeout
|
||||
}
|
||||
|
||||
pub fn get_disabled_main_poisson_packet_distribution(&self) -> bool {
|
||||
self.debug.disable_main_poisson_packet_distribution
|
||||
}
|
||||
|
||||
pub fn get_version(&self) -> &str {
|
||||
&self.client.version
|
||||
}
|
||||
@@ -342,14 +328,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 +353,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(),
|
||||
}
|
||||
@@ -477,6 +451,10 @@ pub struct Debug {
|
||||
/// did not reach its destination.
|
||||
#[serde(with = "humantime_serde")]
|
||||
topology_resolution_timeout: Duration,
|
||||
|
||||
/// Controls whether the main packet stream constantly produces packets according to the predefined
|
||||
/// poisson distribution.
|
||||
disable_main_poisson_packet_distribution: bool,
|
||||
}
|
||||
|
||||
impl Default for Debug {
|
||||
@@ -491,6 +469,7 @@ impl Default for Debug {
|
||||
gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
|
||||
topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE,
|
||||
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
|
||||
disable_main_poisson_packet_distribution: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -121,6 +121,9 @@ impl NymClient {
|
||||
self.config.get_base().get_average_ack_delay(),
|
||||
self.config.get_base().get_message_sending_average_delay(),
|
||||
self.config.get_base().get_average_packet_delay(),
|
||||
self.config
|
||||
.get_base()
|
||||
.get_disabled_main_poisson_packet_distribution(),
|
||||
self.as_mix_recipient(),
|
||||
);
|
||||
|
||||
@@ -190,8 +193,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
|
||||
|
||||
@@ -122,6 +122,9 @@ impl NymClient {
|
||||
self.config.get_base().get_average_ack_delay(),
|
||||
self.config.get_base().get_message_sending_average_delay(),
|
||||
self.config.get_base().get_average_packet_delay(),
|
||||
self.config
|
||||
.get_base()
|
||||
.get_disabled_main_poisson_packet_distribution(),
|
||||
self.as_mix_recipient(),
|
||||
);
|
||||
|
||||
@@ -191,8 +194,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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,5 +52,6 @@ fn setup_logging() {
|
||||
.filter_module("want", log::LevelFilter::Warn)
|
||||
.filter_module("tungstenite", log::LevelFilter::Warn)
|
||||
.filter_module("tokio_tungstenite", log::LevelFilter::Warn)
|
||||
.filter_module("sled", log::LevelFilter::Warn)
|
||||
.init();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -554,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? {
|
||||
@@ -616,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 {
|
||||
|
||||
@@ -63,7 +63,7 @@ impl PacketRouter {
|
||||
} else if received_packet.len()
|
||||
== PacketSize::ExtendedPacket.plaintext_size() - ack_overhead
|
||||
{
|
||||
warn!("received extended packet? Did not expect this...");
|
||||
//warn!("received extended packet? Did not expect this...");
|
||||
received_messages.push(received_packet);
|
||||
} else {
|
||||
// this can happen if other clients are not padding their messages
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -271,6 +271,8 @@ where
|
||||
|
||||
// including set of delays
|
||||
let delays = delays::generate_from_average_duration(route.len(), self.average_packet_delay);
|
||||
//let delays = vec![Delay::new_from_nanos(0); route.len()];
|
||||
//assert!(delays_pre.len() == delays.len());
|
||||
|
||||
// create the actual sphinx packet here. With valid route and correct payload size,
|
||||
// there's absolutely no reason for this call to fail.
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
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
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
];
|
||||
@@ -1,30 +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");
|
||||
// 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.rinkeby.NYM_ERC20,
|
||||
contracts.rinkeby.GRAVITY
|
||||
);
|
||||
|
||||
console.log("deploying...");
|
||||
|
||||
contracts.rinkeby.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,32 +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 [deployer] = await ethers.getSigners();
|
||||
console.log(deployer.address);
|
||||
console.log(constants.ZERO_BYTES32);
|
||||
const Gravity = await ethers.getContractFactory("Gravity");
|
||||
// deploy with args from unit tests
|
||||
const gravity = await Gravity.deploy(
|
||||
constants.ZERO_BYTES32,
|
||||
[deployer.address],
|
||||
[2863311531]
|
||||
);
|
||||
|
||||
contracts.rinkeby.GRAVITY = gravity.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(`gravity.sol deployed at ${gravity.address}`);
|
||||
}
|
||||
|
||||
main()
|
||||
.then(() => process.exit(0))
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
// arguments for verification of gravity contract via hardhat-etherscan plugin
|
||||
module.exports = [
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
["0xf5b1B5C9b89906219Ba29a6cb12F0528c4C25D18"],
|
||||
[2863311531]
|
||||
];
|
||||
@@ -1,7 +0,0 @@
|
||||
// arguments for verification of gravity contract via hardhat-etherscan plugin
|
||||
module.exports = [
|
||||
"0xe58c12f8bA4a28e87841b10BdEdf80A47F86BA9B",
|
||||
"test",
|
||||
"tst",
|
||||
6
|
||||
];
|
||||
@@ -1,266 +0,0 @@
|
||||
const { expect } = require("chai");
|
||||
const { constants, expectRevert, expectEvent } = require('@openzeppelin/test-helpers');
|
||||
const { artifacts, web3 } = require("hardhat");
|
||||
const BN = require('bn.js');
|
||||
const BandwidthGenerator = artifacts.require('BandwidthGenerator');
|
||||
const Gravity = artifacts.require('test-contracts/TestGravity');
|
||||
const CosmosToken = artifacts.require('TestCosmosERC20');
|
||||
|
||||
|
||||
contract('BandwidthGenerator', (accounts) => {
|
||||
let bandwidthGenerator;
|
||||
let gravity;
|
||||
let erc20token;
|
||||
let owner = accounts[0];
|
||||
let user = accounts[1];
|
||||
let cosmosRecipient = "nymt1f06hzmwf9chqewkpv93ajk6tayzp4784m2da9x"; // random sandbox testnet address
|
||||
let initialRatio = 1073741824; // 1073741824 bytes = 1GB
|
||||
let newRatio;
|
||||
let tokenAmount = web3.utils.toWei('100'); // this is converting 100 tokens to their representation in wei: 100000000000000000000
|
||||
let halfTokenAmount = web3.utils.toWei('50');
|
||||
let unevenTokenAmount = web3.utils.toWei('11.5'); // 11500000000000000000
|
||||
let oneToken = web3.utils.toWei('1');
|
||||
|
||||
before('deploy contracts', async () => {
|
||||
|
||||
// deploy gravity bridge with test data
|
||||
gravity = await Gravity.new(
|
||||
constants.ZERO_BYTES32,
|
||||
[owner],
|
||||
[2863311531]
|
||||
);
|
||||
|
||||
// deploy erc20 NYM from bridge
|
||||
await gravity.deployERC20(
|
||||
'cosmosNYMDenomination',
|
||||
'NYMERC20',
|
||||
'NYM',
|
||||
6
|
||||
);
|
||||
|
||||
// grab event args for getting token address
|
||||
const logs = await gravity.getPastEvents({
|
||||
fromBlock: 0,
|
||||
toBlock: "latest",
|
||||
});
|
||||
|
||||
// create contract abstraction of deployed erc20NYM with address from event args
|
||||
erc20token = await CosmosToken.at(logs[0].args._tokenContract);
|
||||
|
||||
// deploy bandwidthGenerator contract with contract address of erc20NYM & address of gravity bridge
|
||||
bandwidthGenerator = await BandwidthGenerator.new(erc20token.address, gravity.address);
|
||||
|
||||
});
|
||||
|
||||
context(">> deployment parameters are valid", () => {
|
||||
it("returns the correct erc20 address", async () => {
|
||||
expect((await bandwidthGenerator.erc20()).toString()).to.equal((erc20token.address).toString());
|
||||
});
|
||||
it("returns the correct gravity address", async () => {
|
||||
expect((await bandwidthGenerator.gravityBridge()).toString()).to.equal((gravity.address).toString());
|
||||
});
|
||||
it("returns the correct initial BytesPerToken ratio", async () => {
|
||||
expect((await bandwidthGenerator.BytesPerToken()).toString()).to.equal((initialRatio).toString());
|
||||
});
|
||||
it("returns the correct contract admin", async () => {
|
||||
expect((await bandwidthGenerator.owner()).toString()).to.equal((owner).toString());
|
||||
});
|
||||
it("returns the correct default generation state: true", async () => {
|
||||
expect((await bandwidthGenerator.credentialGenerationEnabled())).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
context(">> deployment parameters are invalid", () => {
|
||||
it("cannot be deployed with invalid erc20 address (zero address)", async () => {
|
||||
expectRevert(
|
||||
BandwidthGenerator.new(constants.ZERO_ADDRESS, gravity.address),
|
||||
"BandwidthGenerator: erc20 address cannot be null"
|
||||
)
|
||||
});
|
||||
it("cannot be deployed with invalid gravity bridge address (zero address)", async () => {
|
||||
expectRevert(
|
||||
BandwidthGenerator.new(erc20token.address, constants.ZERO_ADDRESS),
|
||||
"BandwidthGenerator: gravity bridge address cannot be null"
|
||||
)
|
||||
});
|
||||
});
|
||||
|
||||
context(">> generateBasicBandwidthCredential()", () => {
|
||||
before("mint tokens & approve", async () => {
|
||||
// transfer tokens to account which will create a BBCredential
|
||||
await erc20token.mintForUnitTesting(user, tokenAmount);
|
||||
// approve transfer to contract
|
||||
await erc20token.approve(bandwidthGenerator.address,(tokenAmount),{ from: user });
|
||||
});
|
||||
|
||||
it("transfers tokens to bridge and emits an event with the correct values: 50 erc20NYM = 50GB of bandwidth", async () => {
|
||||
let tx = await bandwidthGenerator.generateBasicBandwidthCredential(
|
||||
halfTokenAmount,
|
||||
15,
|
||||
[0x39, 0x53, 0x0a, 0x00, 0xea, 0xe2, 0xa5, 0xaa, 0xc8, 0x14, 0x42, 0x09, 0xcc, 0xac, 0x91, 0x7a, 0xe5, 0x6b, 0xf4, 0xa9, 0x58, 0x95, 0x44, 0xcb, 0x00, 0x20, 0xf9, 0x2f, 0xee, 0x35, 0xa3, 0xba,
|
||||
0x39, 0x53, 0x0a, 0x00, 0xea, 0xe2, 0xa5, 0xaa, 0xc8, 0x14, 0x42, 0x09, 0xcc, 0xac, 0x91, 0x7a, 0xe5, 0x6b, 0xf4, 0xa9, 0x58, 0x95, 0x44, 0xcb, 0x00, 0x20, 0xf9, 0x2f, 0xee, 0x35, 0xa3, 0xba],
|
||||
cosmosRecipient,
|
||||
{ from: user }
|
||||
);
|
||||
|
||||
let expectedBandwidthInMB = ((halfTokenAmount/10**18)*initialRatio); // 50 * (1024*1024*1024) bytes = 51200MB = 50GB of bandwidth
|
||||
|
||||
await expectEvent.inTransaction(tx.tx, bandwidthGenerator, 'BBCredentialPurchased', {
|
||||
Bandwidth: expectedBandwidthInMB.toString(),
|
||||
VerificationKey: '15',
|
||||
SignedVerificationKey: '0x39530a00eae2a5aac8144209ccac917ae56bf4a9589544cb0020f92fee35a3ba39530a00eae2a5aac8144209ccac917ae56bf4a9589544cb0020f92fee35a3ba',
|
||||
CosmosRecipient: cosmosRecipient
|
||||
});
|
||||
|
||||
await expectEvent.inTransaction(tx.tx, erc20token, 'Transfer', {
|
||||
from: user,
|
||||
to: bandwidthGenerator.address,
|
||||
});
|
||||
|
||||
await expectEvent.inTransaction(tx.tx, gravity, 'SendToCosmosEvent', {
|
||||
_tokenContract: erc20token.address,
|
||||
_sender: bandwidthGenerator.address,
|
||||
_destination: cosmosRecipient,
|
||||
_amount: halfTokenAmount
|
||||
});
|
||||
|
||||
expect((await erc20token.balanceOf(bandwidthGenerator.address)).toString()).to.equal('0');
|
||||
expect((await erc20token.balanceOf(user)).toString()).to.equal(halfTokenAmount.toString());
|
||||
});
|
||||
|
||||
/**
|
||||
* This can be out by a float still with amounts such as '.1'
|
||||
*/
|
||||
it("it transfers for uneven token amounts", async () => {
|
||||
let tx = await bandwidthGenerator.generateBasicBandwidthCredential(
|
||||
unevenTokenAmount,
|
||||
15,
|
||||
[0x39, 0x53, 0x0a, 0x00, 0xea, 0xe2, 0xa5, 0xaa, 0xc8, 0x14, 0x42, 0x09, 0xcc, 0xac, 0x91, 0x7a, 0xe5, 0x6b, 0xf4, 0xa9, 0x58, 0x95, 0x44, 0xcb, 0x00, 0x20, 0xf9, 0x2f, 0xee, 0x35, 0xa3, 0xba,
|
||||
0x39, 0x53, 0x0a, 0x00, 0xea, 0xe2, 0xa5, 0xaa, 0xc8, 0x14, 0x42, 0x09, 0xcc, 0xac, 0x91, 0x7a, 0xe5, 0x6b, 0xf4, 0xa9, 0x58, 0x95, 0x44, 0xcb, 0x00, 0x20, 0xf9, 0x2f, 0xee, 0x35, 0xa3, 0xba],
|
||||
cosmosRecipient,
|
||||
{ from: user }
|
||||
);
|
||||
|
||||
let newexpectedBandwidthInMB = ((11500000000000000000*initialRatio)/10**18);
|
||||
|
||||
await expectEvent.inTransaction(tx.tx, bandwidthGenerator, 'BBCredentialPurchased', {
|
||||
Bandwidth: newexpectedBandwidthInMB.toString(),
|
||||
VerificationKey: '15',
|
||||
SignedVerificationKey: '0x39530a00eae2a5aac8144209ccac917ae56bf4a9589544cb0020f92fee35a3ba39530a00eae2a5aac8144209ccac917ae56bf4a9589544cb0020f92fee35a3ba',
|
||||
CosmosRecipient: cosmosRecipient
|
||||
});
|
||||
|
||||
await expectEvent.inTransaction(tx.tx, erc20token, 'Transfer', {
|
||||
from: user,
|
||||
to: bandwidthGenerator.address,
|
||||
});
|
||||
|
||||
await expectEvent.inTransaction(tx.tx, gravity, 'SendToCosmosEvent', {
|
||||
_tokenContract: erc20token.address,
|
||||
_sender: bandwidthGenerator.address,
|
||||
_destination: cosmosRecipient,
|
||||
_amount: unevenTokenAmount
|
||||
});
|
||||
});
|
||||
|
||||
it("reverts when signed verification key !=64 bytes", async () => {
|
||||
await erc20token.approve(bandwidthGenerator.address,(halfTokenAmount),{ from: user });
|
||||
|
||||
await expectRevert(
|
||||
bandwidthGenerator.generateBasicBandwidthCredential(
|
||||
1,
|
||||
16,
|
||||
[0x0a, 0x00, 0xea, 0xe2, 0xa5, 0xaa, 0xc8, 0x14, 0x42, 0x09, 0xcc, 0xac, 0x91, 0x7a, 0xe5, 0x6b, 0xf4, 0xa9, 0x58, 0x95, 0x44, 0xcb, 0x00, 0x20, 0xf9, 0x2f, 0xee, 0x35, 0xa3, 0xba,
|
||||
0x39, 0x53, 0x0a, 0x00, 0xea, 0xe2, 0xa5, 0xaa, 0xc8, 0x14, 0x42, 0x09, 0xcc, 0xac, 0x91, 0x7a, 0xe5, 0x6b, 0xf4, 0xa9, 0x58, 0x95, 0x44, 0xcb, 0x00, 0x20, 0xf9, 0x2f, 0xee, 0x35, 0xa3, 0xba],
|
||||
cosmosRecipient,
|
||||
{ from: user }
|
||||
), "BandwidthGenerator: Signature doesn't have 64 bytes"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context(">> changeRatio()", () => {
|
||||
it("only admin can change token to MB ratio", async () => {
|
||||
newRatio = 10 * initialRatio; // 10GB of bandwidth per 1 erc20NYM
|
||||
await expectRevert(
|
||||
bandwidthGenerator.changeRatio(newRatio, {from: user}),
|
||||
"Ownable: caller is not the owner"
|
||||
);
|
||||
});
|
||||
it("admin can change ratio, emits 'RatioChanged' event", async () => {
|
||||
let tx = await bandwidthGenerator.changeRatio(newRatio, {from: owner});
|
||||
await expectEvent.inTransaction(tx.tx, bandwidthGenerator, 'RatioChanged', {
|
||||
NewBytesPerToken: newRatio.toString()
|
||||
});
|
||||
expect((await bandwidthGenerator.BytesPerToken()).toString()).to.equal((newRatio).toString());
|
||||
});
|
||||
it("BBCredential represents new ratio after change: 1 erc20NYM = 10GB of bandwidth", async () => {
|
||||
let tx = await bandwidthGenerator.generateBasicBandwidthCredential(
|
||||
oneToken,
|
||||
15,
|
||||
[0x39, 0x53, 0x0a, 0x00, 0xea, 0xe2, 0xa5, 0xaa, 0xc8, 0x14, 0x42, 0x09, 0xcc, 0xac, 0x91, 0x7a, 0xe5, 0x6b, 0xf4, 0xa9, 0x58, 0x95, 0x44, 0xcb, 0x00, 0x20, 0xf9, 0x2f, 0xee, 0x35, 0xa3, 0xba,
|
||||
0x39, 0x53, 0x0a, 0x00, 0xea, 0xe2, 0xa5, 0xaa, 0xc8, 0x14, 0x42, 0x09, 0xcc, 0xac, 0x91, 0x7a, 0xe5, 0x6b, 0xf4, 0xa9, 0x58, 0x95, 0x44, 0xcb, 0x00, 0x20, 0xf9, 0x2f, 0xee, 0x35, 0xa3, 0xba],
|
||||
cosmosRecipient,
|
||||
{ from: user }
|
||||
);
|
||||
|
||||
let expectedBandwidthInMB = ((oneToken/10**18)*newRatio);
|
||||
|
||||
await expectEvent.inTransaction(tx.tx, bandwidthGenerator, 'BBCredentialPurchased', {
|
||||
Bandwidth: expectedBandwidthInMB.toString(),
|
||||
VerificationKey: '15',
|
||||
SignedVerificationKey: '0x39530a00eae2a5aac8144209ccac917ae56bf4a9589544cb0020f92fee35a3ba39530a00eae2a5aac8144209ccac917ae56bf4a9589544cb0020f92fee35a3ba',
|
||||
CosmosRecipient: cosmosRecipient
|
||||
});
|
||||
|
||||
await expectEvent.inTransaction(tx.tx, erc20token, 'Transfer', {
|
||||
from: user,
|
||||
to: bandwidthGenerator.address,
|
||||
});
|
||||
|
||||
await expectEvent.inTransaction(tx.tx, gravity, 'SendToCosmosEvent', {
|
||||
_tokenContract: erc20token.address,
|
||||
_sender: bandwidthGenerator.address,
|
||||
_destination: cosmosRecipient,
|
||||
_amount: oneToken
|
||||
});
|
||||
});
|
||||
});
|
||||
context(">>credential generation admin switch", () => {
|
||||
it("only admin can switch credential generation off", async () => {
|
||||
await expectRevert(
|
||||
bandwidthGenerator.credentialGenerationSwitch(false, { from: user }),
|
||||
"Ownable: caller is not the owner"
|
||||
)
|
||||
});
|
||||
it("admin can switch credential generation on/off & switch generates an event", async () => {
|
||||
let tx = await bandwidthGenerator.credentialGenerationSwitch(false);
|
||||
expect((await bandwidthGenerator.credentialGenerationEnabled())).to.equal(false);
|
||||
|
||||
await expectEvent.inTransaction(tx.tx, bandwidthGenerator, 'CredentialGenerationSwitch', {
|
||||
Enabled: false,
|
||||
});
|
||||
|
||||
tx = await bandwidthGenerator.credentialGenerationSwitch(true);
|
||||
expect((await bandwidthGenerator.credentialGenerationEnabled())).to.equal(true);
|
||||
|
||||
await expectEvent.inTransaction(tx.tx, bandwidthGenerator, 'CredentialGenerationSwitch', {
|
||||
Enabled: true,
|
||||
});
|
||||
});
|
||||
it("cannot generate credentials if switch = false", async () => {
|
||||
await bandwidthGenerator.credentialGenerationSwitch(false);
|
||||
await expectRevert(
|
||||
bandwidthGenerator.generateBasicBandwidthCredential(
|
||||
oneToken,
|
||||
15,
|
||||
[0x39, 0x53, 0x0a, 0x00, 0xea, 0xe2, 0xa5, 0xaa, 0xc8, 0x14, 0x42, 0x09, 0xcc, 0xac, 0x91, 0x7a, 0xe5, 0x6b, 0xf4, 0xa9, 0x58, 0x95, 0x44, 0xcb, 0x00, 0x20, 0xf9, 0x2f, 0xee, 0x35, 0xa3, 0xba,
|
||||
0x39, 0x53, 0x0a, 0x00, 0xea, 0xe2, 0xa5, 0xaa, 0xc8, 0x14, 0x42, 0x09, 0xcc, 0xac, 0x91, 0x7a, 0xe5, 0x6b, 0xf4, 0xa9, 0x58, 0x95, 0x44, 0xcb, 0x00, 0x20, 0xf9, 0x2f, 0xee, 0x35, 0xa3, 0xba],
|
||||
cosmosRecipient,
|
||||
{ from: user }
|
||||
), "BandwidthGenerator: credential generation isn't currently enabled"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -70,6 +70,7 @@ pub(crate) fn mixnodes<'a>(
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
pub struct StoredMixnodeBond {
|
||||
pub pledge_amount: Coin,
|
||||
|
||||
@@ -554,6 +554,13 @@ pub fn calculate_delegator_reward(
|
||||
.map(|(_, delegation)| delegation)
|
||||
.collect::<Vec<Delegation>>();
|
||||
|
||||
// If last_claimed_height is 0 iterate from earlest_delegation, try avoiding gas query limit
|
||||
let iter_from = if last_claimed_height == 0 {
|
||||
delegations.iter().fold(0, |acc, x| acc.min(x.block_height))
|
||||
} else {
|
||||
last_claimed_height
|
||||
};
|
||||
|
||||
// Accumulate outside of the loop to gain some speed, on a log of checkpoints
|
||||
let mut delegation_at_height = Uint128::zero();
|
||||
|
||||
@@ -563,7 +570,7 @@ pub fn calculate_delegator_reward(
|
||||
.prefix(mix_identity)
|
||||
.keys(
|
||||
storage,
|
||||
Some(Bound::inclusive(last_claimed_height)),
|
||||
Some(Bound::inclusive(iter_from)),
|
||||
None,
|
||||
Order::Ascending,
|
||||
)
|
||||
|
||||
@@ -23,6 +23,7 @@ fn generate_storage_key(storage: &mut dyn Storage) -> Result<u32, ContractError>
|
||||
Ok(key)
|
||||
}
|
||||
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct Account {
|
||||
pub owner_address: Addr,
|
||||
|
||||
+164
-16
@@ -259,7 +259,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "coconut-bandwidth-contract-common"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/nymtech/nym?branch=feature/nym-cli#4045541e461898c37872a9b75822d3ba84b9f98c"
|
||||
source = "git+https://github.com/nymtech/nym?branch=develop#124103d51b8fdd66552c27575234d810e80ec6c8"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"multisig-contract-common",
|
||||
@@ -270,7 +270,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "coconut-interface"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/nymtech/nym?branch=feature/nym-cli#4045541e461898c37872a9b75822d3ba84b9f98c"
|
||||
source = "git+https://github.com/nymtech/nym?branch=develop#124103d51b8fdd66552c27575234d810e80ec6c8"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"getset",
|
||||
@@ -290,10 +290,22 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "comfy-table"
|
||||
version = "6.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85914173c2f558d61613bfbbf1911f14e630895087a7ed2fafc0f5319e1536e7"
|
||||
dependencies = [
|
||||
"crossterm",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "config"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/nymtech/nym?branch=feature/nym-cli#4045541e461898c37872a9b75822d3ba84b9f98c"
|
||||
source = "git+https://github.com/nymtech/nym?branch=develop#124103d51b8fdd66552c27575234d810e80ec6c8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"handlebars",
|
||||
@@ -314,7 +326,7 @@ checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3"
|
||||
[[package]]
|
||||
name = "contracts-common"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/nymtech/nym?branch=feature/nym-cli#4045541e461898c37872a9b75822d3ba84b9f98c"
|
||||
source = "git+https://github.com/nymtech/nym?branch=develop#124103d51b8fdd66552c27575234d810e80ec6c8"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
]
|
||||
@@ -424,6 +436,31 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crossterm_winapi",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"signal-hook",
|
||||
"signal-hook-mio",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm_winapi"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.2"
|
||||
@@ -662,7 +699,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "execute"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/nymtech/nym?branch=feature/nym-cli#4045541e461898c37872a9b75822d3ba84b9f98c"
|
||||
source = "git+https://github.com/nymtech/nym?branch=develop#124103d51b8fdd66552c27575234d810e80ec6c8"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
@@ -1274,6 +1311,16 @@ version = "0.2.132"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390"
|
||||
dependencies = [
|
||||
"autocfg 1.1.0",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.17"
|
||||
@@ -1331,7 +1378,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "mixnet-contract-common"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/nymtech/nym?branch=feature/nym-cli#4045541e461898c37872a9b75822d3ba84b9f98c"
|
||||
source = "git+https://github.com/nymtech/nym?branch=develop#124103d51b8fdd66552c27575234d810e80ec6c8"
|
||||
dependencies = [
|
||||
"az",
|
||||
"contracts-common",
|
||||
@@ -1349,7 +1396,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "multisig-contract-common"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/nymtech/nym?branch=feature/nym-cli#4045541e461898c37872a9b75822d3ba84b9f98c"
|
||||
source = "git+https://github.com/nymtech/nym?branch=develop#124103d51b8fdd66552c27575234d810e80ec6c8"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"cw-utils",
|
||||
@@ -1380,7 +1427,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "network-defaults"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/nymtech/nym?branch=feature/nym-cli#4045541e461898c37872a9b75822d3ba84b9f98c"
|
||||
source = "git+https://github.com/nymtech/nym?branch=develop#124103d51b8fdd66552c27575234d810e80ec6c8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"dotenv",
|
||||
@@ -1433,12 +1480,16 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "nym-cli-commands"
|
||||
version = "1.0.0"
|
||||
source = "git+https://github.com/nymtech/nym?branch=feature/nym-cli#4045541e461898c37872a9b75822d3ba84b9f98c"
|
||||
source = "git+https://github.com/nymtech/nym?branch=develop#124103d51b8fdd66552c27575234d810e80ec6c8"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bip39",
|
||||
"bs58",
|
||||
"cfg-if",
|
||||
"clap",
|
||||
"comfy-table",
|
||||
"cosmrs",
|
||||
"cosmwasm-std",
|
||||
"handlebars",
|
||||
"humantime-serde",
|
||||
"k256",
|
||||
@@ -1449,6 +1500,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"time",
|
||||
"toml",
|
||||
"url",
|
||||
"validator-client",
|
||||
@@ -1458,7 +1510,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "nymcoconut"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/nymtech/nym?branch=feature/nym-cli#4045541e461898c37872a9b75822d3ba84b9f98c"
|
||||
source = "git+https://github.com/nymtech/nym?branch=develop#124103d51b8fdd66552c27575234d810e80ec6c8"
|
||||
dependencies = [
|
||||
"bls12_381",
|
||||
"bs58",
|
||||
@@ -1546,6 +1598,29 @@ dependencies = [
|
||||
"group 0.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec 1.9.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.8"
|
||||
@@ -2045,6 +2120,12 @@ dependencies = [
|
||||
"security-framework",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.11"
|
||||
@@ -2095,6 +2176,12 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "sct"
|
||||
version = "0.6.1"
|
||||
@@ -2260,6 +2347,36 @@ dependencies = [
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"signal-hook-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-mio"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"mio",
|
||||
"signal-hook",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "1.4.0"
|
||||
@@ -2288,6 +2405,12 @@ dependencies = [
|
||||
"maybe-uninit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.4.4"
|
||||
@@ -2326,6 +2449,25 @@ version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.24.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.24.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.4.1"
|
||||
@@ -2698,9 +2840,15 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09c8070a9942f5e7cfccd93f490fdebd230ee3c3c9f107cb25bad5351ef671cf"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
"smallvec 0.6.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.3"
|
||||
@@ -2735,7 +2883,7 @@ checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
|
||||
[[package]]
|
||||
name = "validator-api-requests"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/nymtech/nym?branch=feature/nym-cli#4045541e461898c37872a9b75822d3ba84b9f98c"
|
||||
source = "git+https://github.com/nymtech/nym?branch=develop#124103d51b8fdd66552c27575234d810e80ec6c8"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"coconut-interface",
|
||||
@@ -2750,7 +2898,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "validator-client"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/nymtech/nym?branch=feature/nym-cli#4045541e461898c37872a9b75822d3ba84b9f98c"
|
||||
source = "git+https://github.com/nymtech/nym?branch=develop#124103d51b8fdd66552c27575234d810e80ec6c8"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64",
|
||||
@@ -2797,8 +2945,8 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "vesting-contract"
|
||||
version = "1.0.1"
|
||||
source = "git+https://github.com/nymtech/nym?branch=feature/nym-cli#4045541e461898c37872a9b75822d3ba84b9f98c"
|
||||
version = "1.0.2"
|
||||
source = "git+https://github.com/nymtech/nym?branch=develop#124103d51b8fdd66552c27575234d810e80ec6c8"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
@@ -2812,7 +2960,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "vesting-contract-common"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/nymtech/nym?branch=feature/nym-cli#4045541e461898c37872a9b75822d3ba84b9f98c"
|
||||
source = "git+https://github.com/nymtech/nym?branch=develop#124103d51b8fdd66552c27575234d810e80ec6c8"
|
||||
dependencies = [
|
||||
"config",
|
||||
"cosmwasm-std",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// master APIs
|
||||
export const API_BASE_URL = process.env.EXPLORER_API_URL;
|
||||
export const VALIDATOR_API_BASE_URL = process.env.VALIDATOR_API_URL;
|
||||
export const VALIDATOR_URL = process.env.VALIDATOR_URL;
|
||||
export const { VALIDATOR_URL } = process.env;
|
||||
export const BIG_DIPPER = process.env.BIG_DIPPER_URL;
|
||||
|
||||
// specific API routes
|
||||
|
||||
@@ -47,6 +47,7 @@ const FilterItem = ({
|
||||
scale={scale}
|
||||
min={min}
|
||||
max={max}
|
||||
valueLabelFormat={(val: number) => (val === 100 && id === 'stakeSaturation' ? '>100' : val)}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -26,32 +26,19 @@ export const generateFilterSchema = (upperSaturationValue?: number) => ({
|
||||
label: 'Stake saturation (%)',
|
||||
id: EnumFilterKey.stakeSaturation,
|
||||
value: [0, upperSaturationValue || 100],
|
||||
marks: [
|
||||
{ label: '0', value: 0 },
|
||||
|
||||
{
|
||||
label: '10',
|
||||
value: 10,
|
||||
},
|
||||
|
||||
{
|
||||
label: '50',
|
||||
value: 50,
|
||||
},
|
||||
{ label: '90', value: 90 },
|
||||
|
||||
{
|
||||
label: upperSaturationValue ? `${upperSaturationValue}` : '100',
|
||||
value: upperSaturationValue || 100,
|
||||
},
|
||||
],
|
||||
max: upperSaturationValue,
|
||||
isSmooth: true,
|
||||
marks: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, upperSaturationValue || 100].map((value) => ({
|
||||
value: value < 100 ? value : 100,
|
||||
label: value < 100 ? value : '>100',
|
||||
})),
|
||||
max: 100,
|
||||
tooltipInfo: "Select nodes with <100% saturation. Any additional stake above 100% saturation won't get rewards",
|
||||
},
|
||||
routingScore: {
|
||||
label: 'Routing score (%)',
|
||||
id: EnumFilterKey.routingScore,
|
||||
value: [0, 100],
|
||||
isSmooth: true,
|
||||
marks: [
|
||||
{ label: '0', value: 0 },
|
||||
{ label: '10', value: 10 },
|
||||
|
||||
@@ -9,6 +9,7 @@ export type MixnodeRowType = {
|
||||
identity_key: string;
|
||||
bond: number;
|
||||
self_percentage: string;
|
||||
pledge_amount: number;
|
||||
host: string;
|
||||
layer: string;
|
||||
profit_percentage: string;
|
||||
@@ -35,6 +36,7 @@ export function mixNodeResponseItemToMixnodeRowType(item: MixNodeResponseItem):
|
||||
bond: totalBond || 0,
|
||||
location: item?.location?.country_name || '',
|
||||
self_percentage: selfPercentage,
|
||||
pledge_amount: pledge,
|
||||
host: item?.mix_node?.host || '',
|
||||
layer: item?.layer || '',
|
||||
profit_percentage: `${profitPercentage}%`,
|
||||
|
||||
@@ -188,7 +188,7 @@ export const PageMixnodes: React.FC = () => {
|
||||
/>
|
||||
),
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
width: 190,
|
||||
width: 185,
|
||||
headerAlign: 'left',
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<MuiLink
|
||||
@@ -204,11 +204,11 @@ export const PageMixnodes: React.FC = () => {
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'self_percentage',
|
||||
headerName: 'Self %',
|
||||
width: 110,
|
||||
field: 'pledge_amount',
|
||||
headerName: 'Bond',
|
||||
width: 200,
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
renderHeader: () => <CustomColumnHeading headingTitle="Self %" />,
|
||||
renderHeader: () => <CustomColumnHeading headingTitle="Bond" tooltipInfo="Node operator's share of stake." />,
|
||||
type: 'number',
|
||||
headerAlign: 'left',
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
@@ -217,7 +217,7 @@ export const PageMixnodes: React.FC = () => {
|
||||
component={RRDLink}
|
||||
to={`/network-components/mixnode/${params.row.identity_key}`}
|
||||
>
|
||||
{params.value}%
|
||||
{currencyToString(params.value)}
|
||||
</MuiLink>
|
||||
),
|
||||
},
|
||||
@@ -231,7 +231,7 @@ export const PageMixnodes: React.FC = () => {
|
||||
/>
|
||||
),
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
width: 165,
|
||||
width: 160,
|
||||
headerAlign: 'left',
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<MuiLink
|
||||
@@ -253,7 +253,7 @@ export const PageMixnodes: React.FC = () => {
|
||||
/>
|
||||
),
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
width: 160,
|
||||
width: 165,
|
||||
headerAlign: 'left',
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<MuiLink
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
the theme declaration in index.tsx or the style prop
|
||||
in <DataGrid /> */
|
||||
|
||||
.MuiDataGrid-sortIcon, .MuiDataGrid-menuIcon {
|
||||
.MuiDataGrid-sortIcon, .MuiDataGrid-menuIcon, .MuiDataGrid-iconButtonContainer{
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
+42
-16
@@ -4,7 +4,10 @@
|
||||
[package]
|
||||
name = "nym-gateway"
|
||||
version = "1.0.2"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
]
|
||||
description = "Implementation of the Nym Mixnet Gateway"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
@@ -28,24 +31,31 @@ once_cell = "1.7.2"
|
||||
pretty_env_logger = "0.4"
|
||||
rand = "0.7"
|
||||
serde = { version = "1.0.104", features = ["derive"] }
|
||||
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
|
||||
subtle-encoding = { version = "0.5", features = ["bech32-preview"]}
|
||||
sqlx = { version = "0.5", features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
"macros",
|
||||
"migrate",
|
||||
] }
|
||||
subtle-encoding = { version = "0.5", features = ["bech32-preview"] }
|
||||
thiserror = "1"
|
||||
tokio = { version = "1.19.1", features = [ "rt-multi-thread", "net", "signal", "fs" ] }
|
||||
tokio-stream = { version = "0.1.9", features = [ "fs" ] }
|
||||
tokio = { version = "1.19.1", features = [
|
||||
"rt-multi-thread",
|
||||
"net",
|
||||
"signal",
|
||||
"fs",
|
||||
] }
|
||||
tokio-stream = { version = "0.1.9", features = ["fs"] }
|
||||
tokio-tungstenite = "0.14"
|
||||
tokio-util = { version = "0.7.3", features = [ "codec" ] }
|
||||
url = { version = "2.2", features = [ "serde" ] }
|
||||
web3 = "0.17.0"
|
||||
tokio-util = { version = "0.7.3", features = ["codec"] }
|
||||
url = { version = "2.2", features = ["serde"] }
|
||||
|
||||
# internal
|
||||
coconut-interface = { path = "../common/coconut-interface" , optional = true}
|
||||
coconut-interface = { path = "../common/coconut-interface", optional = true }
|
||||
credentials = { path = "../common/credentials" }
|
||||
config = { path = "../common/config" }
|
||||
crypto = { path = "../common/crypto" }
|
||||
bandwidth-claim-contract = { path = "../common/bandwidth-claim-contract" }
|
||||
gateway-requests = { path = "gateway-requests" }
|
||||
gateway-client = { path = "../common/client-libs/gateway-client" }
|
||||
mixnet-client = { path = "../common/client-libs/mixnet-client" }
|
||||
mixnode-common = { path = "../common/mixnode-common" }
|
||||
network-defaults = { path = "../common/network-defaults" }
|
||||
@@ -53,14 +63,30 @@ nymsphinx = { path = "../common/nymsphinx" }
|
||||
pemstore = { path = "../common/pemstore" }
|
||||
statistics-common = { path = "../common/statistics" }
|
||||
validator-api-requests = { path = "../validator-api/validator-api-requests" }
|
||||
validator-client = { path = "../common/client-libs/validator-client", features = ["nymd-client"] }
|
||||
validator-client = { path = "../common/client-libs/validator-client", features = [
|
||||
"nymd-client",
|
||||
] }
|
||||
version-checker = { path = "../common/version-checker" }
|
||||
|
||||
[features]
|
||||
coconut = ["coconut-interface", "gateway-requests/coconut", "gateway-client/coconut", "credentials/coconut", "validator-api-requests/coconut"]
|
||||
eth = []
|
||||
coconut = [
|
||||
"coconut-interface",
|
||||
"gateway-requests/coconut",
|
||||
"credentials/coconut",
|
||||
"validator-api-requests/coconut",
|
||||
]
|
||||
|
||||
[build-dependencies]
|
||||
tokio = { version = "1.19.1", features = ["rt-multi-thread", "macros"] }
|
||||
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
|
||||
vergen = { version = "5", default-features = false, features = ["build", "git", "rustc", "cargo"] }
|
||||
sqlx = { version = "0.5", features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
"macros",
|
||||
"migrate",
|
||||
] }
|
||||
vergen = { version = "5", default-features = false, features = [
|
||||
"build",
|
||||
"git",
|
||||
"rustc",
|
||||
"cargo",
|
||||
] }
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ async fn main() {
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let database_path = format!("{}/gateway-example.sqlite", out_dir);
|
||||
|
||||
let mut conn = SqliteConnection::connect(&*format!("sqlite://{}?mode=rwc", database_path))
|
||||
let mut conn = SqliteConnection::connect(&format!("sqlite://{}?mode=rwc", database_path))
|
||||
.await
|
||||
.expect("Failed to create SQLx database connection");
|
||||
|
||||
|
||||
@@ -52,15 +52,10 @@ pub struct Init {
|
||||
mnemonic: Option<String>,
|
||||
|
||||
/// Set this gateway to work in a enabled credentials mode that would disallow clients to bypass bandwidth credential requirement
|
||||
#[cfg(any(feature = "eth", feature = "coconut"))]
|
||||
#[cfg(feature = "coconut")]
|
||||
#[clap(long)]
|
||||
enabled_credentials_mode: Option<bool>,
|
||||
|
||||
/// URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
#[clap(long)]
|
||||
eth_endpoint: String,
|
||||
|
||||
/// Enable/disable gateway anonymized statistics that get sent to a statistics aggregator server
|
||||
#[clap(long)]
|
||||
enabled_statistics: Option<bool>,
|
||||
@@ -83,12 +78,9 @@ impl From<Init> for OverrideConfig {
|
||||
validators: init_config.validators,
|
||||
mnemonic: init_config.mnemonic,
|
||||
|
||||
#[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_endpoint: Some(init_config.eth_endpoint),
|
||||
|
||||
enabled_statistics: init_config.enabled_statistics,
|
||||
statistics_service_url: init_config.statistics_service_url,
|
||||
}
|
||||
@@ -177,10 +169,8 @@ mod tests {
|
||||
mnemonic: None,
|
||||
statistics_service_url: None,
|
||||
enabled_statistics: None,
|
||||
#[cfg(any(feature = "eth", feature = "coconut"))]
|
||||
#[cfg(feature = "coconut")]
|
||||
enabled_credentials_mode: None,
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
eth_endpoint: "".to_string(),
|
||||
};
|
||||
std::env::set_var(BECH32_PREFIX, "n");
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user