-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #398 from Concordium/derive-macros
Procedural macros for creating constant values from strings
- Loading branch information
Showing
71 changed files
with
643 additions
and
103 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
name = "concordium-cis2" | ||
version = "6.1.0" | ||
authors = ["Concordium <[email protected]>"] | ||
rust-version = "1.73" | ||
edition = "2021" | ||
license = "MPL-2.0" | ||
description = "A collection of types for implementing CIS-2 Concordium Token Standard." | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# Changelog | ||
|
||
## Unreleased changes | ||
|
||
## concordium-std-derive 6.0.0 (2024-04-04) | ||
|
||
- Macros for creating constant values from strings for the following types: | ||
- `AccountAddress` | ||
- `ContractAddress` | ||
- `ModuleReference` | ||
- `PublicKeyEcdsaSecp256k1` | ||
- `PublicKeyEd25519` | ||
- `SignatureEcdsaSecp256k1` | ||
- `SignatureEd25519` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
[package] | ||
name = "concordium-std-derive" | ||
authors = ["Concordium <[email protected]>"] | ||
version = "6.0.0" | ||
edition = "2021" | ||
rust-version = "1.73" | ||
license = "MPL-2.0" | ||
description = "Procedural macros to generate concordium type constants from strings, that makes writing smart contracts on the Concordium blockchain easier." | ||
homepage = "https://github.com/Concordium/concordium-rust-smart-contracts/" | ||
repository = "https://github.com/Concordium/concordium-rust-smart-contracts/" | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
[lib] | ||
proc-macro = true | ||
|
||
[dependencies] | ||
concordium-contracts-common = { path = "../concordium-rust-sdk/concordium-base/smart-contracts/contracts-common/concordium-contracts-common", features = [ "derive-serde" ], version = "9" } | ||
syn = "2.0" | ||
proc-macro2 = "1.0" | ||
quote = "1.0" | ||
|
||
[dev-dependencies] | ||
trybuild = "1.0" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,264 @@ | ||
extern crate proc_macro; | ||
|
||
use concordium_contracts_common::{ | ||
AccountAddress, ContractAddress, ModuleReference, PublicKeyEcdsaSecp256k1, PublicKeyEd25519, | ||
SignatureEcdsaSecp256k1, SignatureEd25519, | ||
}; | ||
use proc_macro::TokenStream; | ||
use proc_macro2::{Group, Punct, Spacing, Span}; | ||
use quote::{quote, ToTokens}; | ||
use std::str::FromStr; | ||
use syn::LitStr; | ||
|
||
// Helper functions: | ||
|
||
// Tokenizes a slice of bytes. | ||
fn tokenize_slice(slice: &[u8]) -> proc_macro2::TokenStream { | ||
let mut t = proc_macro2::TokenStream::new(); | ||
for byte in slice { | ||
byte.to_tokens(&mut t); | ||
Punct::new(',', Spacing::Alone).to_tokens(&mut t); | ||
} | ||
Group::new(proc_macro2::Delimiter::Bracket, t).to_token_stream() | ||
} | ||
|
||
// Parses the input tokens of a proc macro and returns the given error | ||
// message on parse error. | ||
fn parse_input(item: TokenStream, msg: &str) -> syn::Result<LitStr> { | ||
match syn::parse::<LitStr>(item) { | ||
Ok(string_literal) => Ok(string_literal), | ||
Err(_) => Err(syn::Error::new(Span::call_site(), msg)), | ||
} | ||
} | ||
|
||
// Parses the given tokens and transforms them using the provided worker | ||
// function. If parsing fails, then an error with the given message is produced. | ||
fn get_token_res( | ||
item: TokenStream, | ||
msg: &str, | ||
worker_fun: impl FnOnce(String, Span) -> syn::Result<TokenStream>, | ||
) -> syn::Result<TokenStream> { | ||
let input = parse_input(item, msg)?; | ||
worker_fun(input.value(), input.span()) | ||
} | ||
|
||
// Parses the given tokens, looks up an environment variable with the parsed | ||
// input as key and transforms the corresponding value using the provided worker | ||
// function. If parsing fails, then an error with the given message is produced. | ||
fn get_token_res_env( | ||
item: TokenStream, | ||
msg: &str, | ||
worker_fun: impl FnOnce(String, Span) -> syn::Result<TokenStream>, | ||
) -> syn::Result<TokenStream> { | ||
let input = parse_input(item, msg)?; | ||
let environment_var_value = match std::env::var(input.value()) { | ||
Ok(value) => value, | ||
Err(e) => { | ||
return Err(syn::Error::new( | ||
input.span(), | ||
format!("Environment variable error: {:?}", e), | ||
)) | ||
} | ||
}; | ||
worker_fun(environment_var_value, input.span()) | ||
} | ||
|
||
fn unwrap_or_report(res: syn::Result<TokenStream>) -> TokenStream { | ||
res.unwrap_or_else(|e| e.into_compile_error().into()) | ||
} | ||
|
||
// Worker functions | ||
|
||
fn acc_address_worker(str: String, span: Span) -> syn::Result<TokenStream> { | ||
let address = match AccountAddress::from_str(&str) { | ||
Ok(addr) => tokenize_slice(&addr.0), | ||
Err(e) => return Err(syn::Error::new(span, format!("Invalid account address: {}", e))), | ||
}; | ||
|
||
Ok(quote!(concordium_std::AccountAddress(#address)).into()) | ||
} | ||
|
||
fn pubkey_ed25519_worker(str: String, span: Span) -> syn::Result<TokenStream> { | ||
let public_key = match PublicKeyEd25519::from_str(&str) { | ||
Ok(pk) => tokenize_slice(&pk.0), | ||
Err(e) => return Err(syn::Error::new(span, format!("Invalid Ed25519 public key: {}", e))), | ||
}; | ||
|
||
Ok(quote!(concordium_std::PublicKeyEd25519(#public_key)).into()) | ||
} | ||
|
||
fn pubkey_ecdsa_worker(str: String, span: Span) -> syn::Result<TokenStream> { | ||
let public_key = match PublicKeyEcdsaSecp256k1::from_str(&str) { | ||
Ok(pk) => tokenize_slice(&pk.0), | ||
Err(e) => return Err(syn::Error::new(span, format!("Invalid ECDSA public key: {}", e))), | ||
}; | ||
|
||
Ok(quote!(concordium_std::PublicKeyEcdsaSecp256k1(#public_key)).into()) | ||
} | ||
|
||
fn signature_ed25519_worker(str: String, span: Span) -> syn::Result<TokenStream> { | ||
let signature = match SignatureEd25519::from_str(&str) { | ||
Ok(sig) => tokenize_slice(&sig.0), | ||
Err(e) => return Err(syn::Error::new(span, format!("Invalid Ed25519 signature: {}", e))), | ||
}; | ||
|
||
Ok(quote!(concordium_std::SignatureEd25519(#signature)).into()) | ||
} | ||
|
||
fn signature_ecdsa_worker(str: String, span: Span) -> syn::Result<TokenStream> { | ||
let signature = match SignatureEcdsaSecp256k1::from_str(&str) { | ||
Ok(sig) => tokenize_slice(&sig.0), | ||
Err(e) => return Err(syn::Error::new(span, format!("Invalid ECDSA signature: {}", e))), | ||
}; | ||
|
||
Ok(quote!(concordium_std::SignatureEcdsaSecp256k1(#signature)).into()) | ||
} | ||
|
||
fn contract_address_worker(str: String, span: Span) -> syn::Result<TokenStream> { | ||
let (index, subindex) = match ContractAddress::from_str(&str) { | ||
Ok(con) => (con.index, con.subindex), | ||
Err(e) => return Err(syn::Error::new(span, format!("Invalid contract address: {}", e))), | ||
}; | ||
|
||
Ok(quote!(concordium_std::ContractAddress::new(#index, #subindex)).into()) | ||
} | ||
|
||
fn module_ref_worker(str: String, span: Span) -> syn::Result<TokenStream> { | ||
let module_ref = match ModuleReference::from_str(&str) { | ||
Ok(mod_ref) => tokenize_slice(&mod_ref.bytes), | ||
Err(e) => return Err(syn::Error::new(span, format!("Invalid module reference: {}", e))), | ||
}; | ||
|
||
Ok(quote!(concordium_std::ModuleReference::new(#module_ref)).into()) | ||
} | ||
|
||
/// Procedural macro for instantiating account addresses. | ||
/// Input must be a valid base58-encoding. | ||
#[proc_macro] | ||
pub fn account_address(item: TokenStream) -> TokenStream { | ||
let msg = "Expected a string literal of a hex-encoded account address"; | ||
unwrap_or_report(get_token_res(item, msg, acc_address_worker)) | ||
} | ||
|
||
/// Procedural macro for instantiating account addresses from an environment | ||
/// variable. Input must be the key of an environment variable whose value is | ||
/// set to a valid base58-encoding of an account address. | ||
#[proc_macro] | ||
pub fn account_address_env(item: TokenStream) -> TokenStream { | ||
let msg = "Expected a string literal of a hex-encoded account address"; | ||
unwrap_or_report(get_token_res_env(item, msg, acc_address_worker)) | ||
} | ||
|
||
/// Procedural macro for instantiating `PublicKeyEd25519` public keys. | ||
/// Input must be a (case-insensitive) hex-encoding and have a length of 64 | ||
/// characters representing 32 bytes. | ||
#[proc_macro] | ||
pub fn public_key_ed25519(item: TokenStream) -> TokenStream { | ||
let msg = "Expected a string literal of a hex-encoded ED25519 public key"; | ||
unwrap_or_report(get_token_res(item, msg, pubkey_ed25519_worker)) | ||
} | ||
|
||
/// Procedural macro for instantiating `PublicKeyEd25519` public keys from | ||
/// an environment variable. Input must be the key of an environment variable | ||
/// whose value is set to a hex-encoded public key which must have a length of | ||
/// 64 characters representing 32 bytes. | ||
#[proc_macro] | ||
pub fn public_key_ed25519_env(item: TokenStream) -> TokenStream { | ||
let msg = "Expected a string literal of a hex-encoded ED25519 public key"; | ||
unwrap_or_report(get_token_res_env(item, msg, pubkey_ed25519_worker)) | ||
} | ||
|
||
/// Procedural macro for instantiating `PublicKeyEcdsaSecp256k1` public keys. | ||
/// Input must be a (case-insensitive) hex-encoding and have a length of 66 | ||
/// characters representing 33 bytes. | ||
#[proc_macro] | ||
pub fn public_key_ecdsa(item: TokenStream) -> TokenStream { | ||
let msg = "Expected a string literal of a hex-encoded ECDSA public key"; | ||
unwrap_or_report(get_token_res(item, msg, pubkey_ecdsa_worker)) | ||
} | ||
|
||
/// Procedural macro for instantiating `PublicKeyEcdsaSecp256k1` public keys | ||
/// from an environment variable. Input must be the key of an environment | ||
/// variable whose value is set to a hex-encoded public key which must have a | ||
/// length of 66 characters representing 33 bytes. | ||
#[proc_macro] | ||
pub fn public_key_ecdsa_env(item: TokenStream) -> TokenStream { | ||
let msg = "Expected a string literal of a hex-encoded ECDSA public key"; | ||
unwrap_or_report(get_token_res_env(item, msg, pubkey_ecdsa_worker)) | ||
} | ||
|
||
/// Procedural macro for instantiating `SignatureEd25519` signatures. | ||
/// Input must be a (case-insensitive) hex-encoding and have a length of 128 | ||
/// characters representing 64 bytes. | ||
#[proc_macro] | ||
pub fn signature_ed25519(item: TokenStream) -> TokenStream { | ||
let msg = "Expected a string literal of a hex-encoded ED25519 signature"; | ||
unwrap_or_report(get_token_res(item, msg, signature_ed25519_worker)) | ||
} | ||
|
||
/// Procedural macro for instantiating `SignatureEd25519` signatures from | ||
/// an environment variable. Input must be the key of an environment variable | ||
/// whose value is set to a hex-encoded signature which must have a length of | ||
/// 128 characters representing 64 bytes. | ||
#[proc_macro] | ||
pub fn signature_ed25519_env(item: TokenStream) -> TokenStream { | ||
let msg = "Expected a string literal of a hex-encoded ED25519 signature"; | ||
unwrap_or_report(get_token_res_env(item, msg, signature_ed25519_worker)) | ||
} | ||
|
||
/// Procedural macro for instantiating `SignatureEcdsaSecp256k1` signatures. | ||
/// Input must be a (case-insensitive) hex-encoding and have a length of 128 | ||
/// characters representing 64 bytes. | ||
#[proc_macro] | ||
pub fn signature_ecdsa(item: TokenStream) -> TokenStream { | ||
let msg = "Expected a string literal of a hex-encoded ECDSA signature"; | ||
unwrap_or_report(get_token_res(item, msg, signature_ecdsa_worker)) | ||
} | ||
|
||
/// Procedural macro for instantiating `SignatureEcdsaSecp256k1` signatures from | ||
/// an environment variable. Input must be the key of an environment variable | ||
/// whose value is set to a hex-encoded signature which must have a length of | ||
/// 128 characters representing 64 bytes. | ||
#[proc_macro] | ||
pub fn signature_ecdsa_env(item: TokenStream) -> TokenStream { | ||
let msg = "Expected a string literal a hex-encoded ECDSA signature"; | ||
unwrap_or_report(get_token_res_env(item, msg, signature_ecdsa_worker)) | ||
} | ||
|
||
/// Procedural macro for instantiating contract addresses. | ||
/// Input must be of the form "<index,subindex>", where index and subindex | ||
/// are integers. | ||
#[proc_macro] | ||
pub fn contract_address(item: TokenStream) -> TokenStream { | ||
let msg = "Expected string literal of a contract address in the form of \"<index,subindex>\""; | ||
unwrap_or_report(get_token_res(item, msg, contract_address_worker)) | ||
} | ||
|
||
/// Procedural macro for instantiating contract addresses from an environment | ||
/// variable. Input must be the key of an environment variable whose value is | ||
/// set to a contract address of the form "<index,subindex>", where index and | ||
/// subindex are integers | ||
#[proc_macro] | ||
pub fn contract_address_env(item: TokenStream) -> TokenStream { | ||
let msg = "Expected string literal of a contract address in the form of \"<index,subindex>\""; | ||
unwrap_or_report(get_token_res_env(item, msg, contract_address_worker)) | ||
} | ||
|
||
/// Procedural macro for instantiating module references. | ||
/// Input must be a (case-insensitive) hex-encoding and have a length of 64 | ||
/// characters representing 32 bytes. | ||
#[proc_macro] | ||
pub fn module_reference(item: TokenStream) -> TokenStream { | ||
let msg = "Expected string literal of a hex-encoded module reference"; | ||
unwrap_or_report(get_token_res(item, msg, module_ref_worker)) | ||
} | ||
|
||
/// Procedural macro for instantiating module references from an environment | ||
/// variable. Input must be the key of an environment variable whose value is | ||
/// set to a hex-encoded module reference which must have a length of 64 | ||
/// characters representing 32 bytes. | ||
#[proc_macro] | ||
pub fn module_reference_env(item: TokenStream) -> TokenStream { | ||
let msg = "Expected string literal of a hex-encoded module reference"; | ||
unwrap_or_report(get_token_res_env(item, msg, module_ref_worker)) | ||
} |
12 changes: 12 additions & 0 deletions
12
concordium-std-derive/tests/test-programs/fail-acc-invalid-base58.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
//! Ensure that the macros generate compilable code. | ||
|
||
use concordium_contracts_common::*; | ||
use concordium_std_derive::*; | ||
|
||
const ACC: AccountAddress = account_address!("30Bx2h5Y2veb4hZgAJWPrr8RyQESKm5TjzF3ti1QQ4VSYLwK1G"); | ||
|
||
fn f() { | ||
println!("{:?}", ACC.to_string()); | ||
} | ||
|
||
fn main() {} |
5 changes: 5 additions & 0 deletions
5
concordium-std-derive/tests/test-programs/fail-acc-invalid-base58.stderr
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
error: Invalid account address: Invalid Base58Check encoding. | ||
--> tests/test-programs/fail-acc-invalid-base58.rs:6:46 | ||
| | ||
6 | const ACC: AccountAddress = account_address!("30Bx2h5Y2veb4hZgAJWPrr8RyQESKm5TjzF3ti1QQ4VSYLwK1G"); | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
12 changes: 12 additions & 0 deletions
12
concordium-std-derive/tests/test-programs/fail-acc-multiple-arguments.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
//! Ensure that the macros generate compilable code. | ||
|
||
use concordium_contracts_common::*; | ||
use concordium_std_derive::*; | ||
|
||
const ACC: AccountAddress = account_address!("3kBx2h5Y2veb4hZgAJWPrr8RyQESKm5TjzF3ti1QQ4VSYLwK1G", "3kBx2h5Y2veb4hZgAJWPrr8RyQESKm5TjzF3ti1QQ4VSYLwK1G"); | ||
|
||
fn f() { | ||
println!("{:?}", ACC.to_string()); | ||
} | ||
|
||
fn main() {} |
7 changes: 7 additions & 0 deletions
7
concordium-std-derive/tests/test-programs/fail-acc-multiple-arguments.stderr
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
error: Expected a string literal of a hex-encoded account address | ||
--> tests/test-programs/fail-acc-multiple-arguments.rs:6:29 | ||
| | ||
6 | ...ss = account_address!("3kBx2h5Y2veb4hZgAJWPrr8RyQESKm5TjzF3ti1QQ4VSYLwK1G", "3kBx2h5Y2veb4hZgAJWPrr8RyQESKm5TjzF3ti1QQ4VSYLwK1G"); | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
| | ||
= note: this error originates in the macro `account_address` (in Nightly builds, run with -Z macro-backtrace for more info) |
Oops, something went wrong.