Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
720 changes: 720 additions & 0 deletions src/abi/antelope.rs

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions src/abi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::sema::ast::Namespace;
use crate::Target;

pub mod anchor;
pub mod antelope;
pub mod ethereum;
pub mod polkadot;
mod tests;
Expand Down Expand Up @@ -41,6 +42,18 @@ pub fn generate_abi(

(serde_json::to_string_pretty(&idl).unwrap(), "json")
}
Target::Antelope => {
if verbose {
eprintln!(
"info: Generating Antelope ABI for contract {}",
ns.contracts[contract_no].id
);
}

let abi = antelope::gen_abi(contract_no, ns);

(serde_json::to_string_pretty(&abi).unwrap(), "abi")
}
_ => {
if verbose {
eprintln!(
Expand Down
9 changes: 5 additions & 4 deletions src/bin/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ pub enum Commands {

#[derive(Args)]
pub struct New {
#[arg(name = "TARGETNAME",required= true, long = "target", value_parser = ["solana", "polkadot", "evm"], help = "Target to build for [possible values: solana, polkadot]", num_args = 1, hide_possible_values = true)]
#[arg(name = "TARGETNAME",required= true, long = "target", value_parser = ["solana", "polkadot", "evm", "antelope"], help = "Target to build for [possible values: solana, polkadot]", num_args = 1, hide_possible_values = true)]
pub target_name: String,

#[arg(name = "INPUT", help = "Name of the project", num_args = 1, value_parser = ValueParser::os_string())]
Expand Down Expand Up @@ -261,7 +261,7 @@ pub struct CompilerOutput {

#[derive(Args)]
pub struct TargetArg {
#[arg(name = "TARGET",required= true, long = "target", value_parser = ["solana", "polkadot", "evm"], help = "Target to build for [possible values: solana, polkadot]", num_args = 1, hide_possible_values = true)]
#[arg(name = "TARGET",required= true, long = "target", value_parser = ["solana", "polkadot", "evm", "antelope"], help = "Target to build for [possible values: solana, polkadot]", num_args = 1, hide_possible_values = true)]
pub name: String,

#[arg(name = "ADDRESS_LENGTH", help = "Address length on the Polkadot Parachain", long = "address-length", num_args = 1, value_parser = value_parser!(u64).range(4..1024))]
Expand All @@ -273,7 +273,7 @@ pub struct TargetArg {

#[derive(Args, Deserialize, Debug, PartialEq)]
pub struct CompileTargetArg {
#[arg(name = "TARGET", long = "target", value_parser = ["solana", "polkadot", "evm", "soroban"], help = "Target to build for [possible values: solana, polkadot]", num_args = 1, hide_possible_values = true)]
#[arg(name = "TARGET", long = "target", value_parser = ["solana", "polkadot", "evm", "soroban", "antelope"], help = "Target to build for [possible values: solana, polkadot]", num_args = 1, hide_possible_values = true)]
pub name: Option<String>,

#[arg(name = "ADDRESS_LENGTH", help = "Address length on the Polkadot Parachain", long = "address-length", num_args = 1, value_parser = value_parser!(u64).range(4..1024))]
Expand Down Expand Up @@ -449,7 +449,7 @@ impl TargetArgTrait for CompileTargetArg {
pub(crate) fn target_arg<T: TargetArgTrait>(target_arg: &T) -> Target {
let target_name = target_arg.get_name();

if target_name == "solana" || target_name == "evm" {
if target_name == "solana" || target_name == "evm" || target_name == "antelope" {
if target_arg.get_address_length().is_some() {
eprintln!("error: address length cannot be modified except for polkadot target");
exit(1);
Expand All @@ -469,6 +469,7 @@ pub(crate) fn target_arg<T: TargetArgTrait>(target_arg: &T) -> Target {
},
"evm" => solang::Target::EVM,
"soroban" => solang::Target::Soroban,
"antelope" => solang::Target::Antelope,
_ => unreachable!(),
};

Expand Down
7 changes: 7 additions & 0 deletions src/codegen/dispatch/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,12 @@ pub(super) fn function_dispatch(
polkadot::function_dispatch(contract_no, all_cfg, ns, opt)
}
Target::Soroban => soroban::function_dispatch(contract_no, all_cfg, ns, opt),
// Antelope's entry point is the hand-written `apply` (see emit/antelope), which
// dispatches actions by name — it does not use these selector-based dispatch CFGs.
// Generating the Polkadot ones only emitted dead functions whose Polkadot-specific
// terminators (ReturnCode / the ReturnData success path) Antelope's emit doesn't
// lower, leaving blocks unterminated → invalid IR that crashed the backend at
// -O none/less (masked at -O default only because global_dce drops dead functions).
Target::Antelope => vec![],
}
}
97 changes: 97 additions & 0 deletions src/codegen/events/antelope.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// SPDX-License-Identifier: Apache-2.0

use crate::codegen::cfg::{ControlFlowGraph, Instr};
use crate::codegen::encoding::abi_encode;
use crate::codegen::events::EventEmitter;
use crate::codegen::expression::expression;
use crate::codegen::vartable::Vartable;
use crate::codegen::{Expression, Options};
use crate::emit::antelope::string_to_name;
use crate::sema::ast::{self, Function, Namespace, Type};
use solang_parser::pt;

/// Antelope event emitter.
///
/// Events are emitted as inline actions (send_inline) to the contract itself.
/// topics[0] = eosio::name(event_name) as uint64 — used as the action name.
/// data = ABI-encoded event fields — used as the action data.
pub(super) struct AntelopeEventEmitter<'a> {
pub(super) args: &'a [ast::Expression],
pub(super) ns: &'a Namespace,
pub(super) event_no: usize,
}

impl EventEmitter for AntelopeEventEmitter<'_> {
fn selector(&self, _emitting_contract_no: usize) -> Vec<u8> {
let event = &self.ns.events[self.event_no];
event.id.name.as_bytes().to_vec()
}

fn emit(
&self,
contract_no: usize,
func: &Function,
cfg: &mut ControlFlowGraph,
vartab: &mut Vartable,
opt: &Options,
) {
let loc = pt::Loc::Builtin;
let event = &self.ns.events[self.event_no];

// Encode the event name as eosio::name uint64 at compile time.
// This will be used as the action name in the send_inline call.
let event_name = &event.id.name;
// Antelope names: max 12 chars, lowercase + 1-5 + dot.
// Prefix with "e." to avoid collisions with real contract action names.
// E.g. event "Mint" → "e.mint", "Transfer" → "e.transfer".
let action_name: String = std::iter::once('e')
.chain(std::iter::once('.'))
.chain(
event_name
.to_lowercase()
.chars()
.filter(|c| c.is_ascii_lowercase() || c.is_ascii_digit())
)
.take(12)
.collect();
let name_encoded = string_to_name(&action_name);

let topics = vec![Expression::NumberLiteral {
loc,
ty: Type::Uint(64),
value: name_encoded.into(),
}];

// Evaluate and ABI-encode all event fields.
let data: Vec<Expression> = self
.args
.iter()
.map(|e| expression(e, cfg, contract_no, Some(func), self.ns, vartab, opt))
.collect();

let encoded_data = if data.is_empty() {
Expression::AllocDynamicBytes {
loc,
ty: Type::DynamicBytes,
size: Expression::NumberLiteral {
loc,
ty: Type::Uint(32),
value: 0.into(),
}
.into(),
initializer: Some(Vec::new()),
}
} else {
abi_encode(&loc, data, self.ns, vartab, cfg, false).0
};

cfg.add(
vartab,
Instr::EmitEvent {
event_no: self.event_no,
data: encoded_data,
topics,
},
);
}
}
4 changes: 4 additions & 0 deletions src/codegen/events/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// SPDX-License-Identifier: Apache-2.0

mod antelope;
mod polkadot;
mod solana;

use crate::codegen::cfg::ControlFlowGraph;
use crate::codegen::events::antelope::AntelopeEventEmitter;
use crate::codegen::events::polkadot::PolkadotEventEmitter;
use crate::codegen::events::solana::SolanaEventEmitter;
use crate::codegen::vartable::Vartable;
Expand Down Expand Up @@ -52,5 +54,7 @@ pub(super) fn new_event_emitter<'a>(
}),

Target::Soroban => todo!(),

Target::Antelope => Box::new(AntelopeEventEmitter { args, ns, event_no }),
}
}
82 changes: 81 additions & 1 deletion src/codegen/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1075,6 +1075,86 @@ pub fn expression(
value,
}
}
ast::Expression::Builtin {
loc,
kind: ast::Builtin::AntelopeName,
args,
..
} => {
// Constant-fold antelope.name("string") → u64 at compile time.
// Extract the string literal from the argument.
// Solang may represent string literals as BytesLiteral or AllocDynamicBytes.
let name_str = match &args[0] {
ast::Expression::BytesLiteral { value, .. } => {
String::from_utf8(value.clone()).unwrap_or_default()
}
ast::Expression::AllocDynamicBytes {
init: Some(value), ..
} => String::from_utf8(value.clone()).unwrap_or_default(),
_ => String::new(),
};
let encoded = crate::emit::antelope::string_to_name(&name_str);
Expression::NumberLiteral {
loc: *loc,
ty: Type::Uint(64),
value: encoded.into(),
}
}
ast::Expression::Builtin {
loc,
kind: ast::Builtin::AntelopeRequireAuth,
args,
..
} => {
let arg = expression(&args[0], cfg, contract_no, func, ns, vartab, opt);
let res = vartab.temp_anonymous(&Type::Uint(64));
cfg.add(
vartab,
Instr::Set {
loc: *loc,
res,
expr: Expression::Builtin {
loc: *loc,
tys: vec![Type::Uint(64)],
kind: Builtin::AntelopeRequireAuth,
args: vec![arg],
},
},
);
Expression::Poison
}
// Void side-effect builtins: must be wrapped in Instr::Set so LLVM emits them.
// Pattern: dummy Uint(64) return type, temp variable discarded, returns Poison.
ast::Expression::Builtin {
loc,
kind: kind @ (ast::Builtin::AntelopeCall
| ast::Builtin::AntelopeCallAuth
| ast::Builtin::AntelopeRequireRecipient
| ast::Builtin::AntelopeSetPayer
| ast::Builtin::AntelopeRequireAuth2),
args,
..
} => {
let lowered_args: Vec<Expression> = args
.iter()
.map(|a| expression(a, cfg, contract_no, func, ns, vartab, opt))
.collect();
let res = vartab.temp_anonymous(&Type::Uint(64));
cfg.add(
vartab,
Instr::Set {
loc: *loc,
res,
expr: Expression::Builtin {
loc: *loc,
tys: vec![Type::Uint(64)],
kind: kind.into(),
args: lowered_args,
},
},
);
Expression::Poison
}
ast::Expression::Builtin {
loc,
tys,
Expand Down Expand Up @@ -3827,7 +3907,7 @@ fn array_subscript(
expr: Box::new(array),
index: Box::new(index),
},
Target::Polkadot { .. } => Expression::Keccak256 {
Target::Polkadot { .. } | Target::Antelope => Expression::Keccak256 {
loc: *loc,
ty: array_ty.clone(),
exprs: vec![array, index],
Expand Down
58 changes: 58 additions & 0 deletions src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1868,6 +1868,35 @@ pub enum Builtin {
ExtendTtl,
ExtendInstanceTtl,
AccessMapping,
AntelopeRequireAuth,
AntelopeSelf,
AntelopeCode,
AntelopeName,
AntelopeRequireRecipient,
AntelopeCall,
AntelopeCallAuth,
AntelopeSetPayer,
AntelopePack,
AntelopeHasAuth,
AntelopeRequireAuth2,
AntelopeTimestamp,
AntelopeToUint64,
AntelopeToInt64,
AntelopeToUint32,
AntelopeToUint128,
AntelopeToBytes32,
AntelopeToString,
AntelopeDbFind,
AntelopeDbGet,
AntelopeDbNext,
AntelopeDbLowerbound,
AntelopeLastPk,
AntelopeDbIdx64Find,
AntelopeDbIdx64Lowerbound,
AntelopeDbIdx128Find,
AntelopeDbIdx128Lowerbound,
AntelopeDbIdx256Find,
AntelopeDbIdx256Lowerbound,
}

impl From<&ast::Builtin> for Builtin {
Expand Down Expand Up @@ -1934,6 +1963,35 @@ impl From<&ast::Builtin> for Builtin {
ast::Builtin::AuthAsCurrContract => Builtin::AuthAsCurrContract,
ast::Builtin::ExtendTtl => Builtin::ExtendTtl,
ast::Builtin::ExtendInstanceTtl => Builtin::ExtendInstanceTtl,
ast::Builtin::AntelopeRequireAuth => Builtin::AntelopeRequireAuth,
ast::Builtin::AntelopeSelf => Builtin::AntelopeSelf,
ast::Builtin::AntelopeCode => Builtin::AntelopeCode,
ast::Builtin::AntelopeName => Builtin::AntelopeName,
ast::Builtin::AntelopeRequireRecipient => Builtin::AntelopeRequireRecipient,
ast::Builtin::AntelopeCall => Builtin::AntelopeCall,
ast::Builtin::AntelopeCallAuth => Builtin::AntelopeCallAuth,
ast::Builtin::AntelopeSetPayer => Builtin::AntelopeSetPayer,
ast::Builtin::AntelopePack => Builtin::AntelopePack,
ast::Builtin::AntelopeHasAuth => Builtin::AntelopeHasAuth,
ast::Builtin::AntelopeRequireAuth2 => Builtin::AntelopeRequireAuth2,
ast::Builtin::AntelopeTimestamp => Builtin::AntelopeTimestamp,
ast::Builtin::AntelopeToUint64 => Builtin::AntelopeToUint64,
ast::Builtin::AntelopeToInt64 => Builtin::AntelopeToInt64,
ast::Builtin::AntelopeToUint32 => Builtin::AntelopeToUint32,
ast::Builtin::AntelopeToUint128 => Builtin::AntelopeToUint128,
ast::Builtin::AntelopeToBytes32 => Builtin::AntelopeToBytes32,
ast::Builtin::AntelopeToString => Builtin::AntelopeToString,
ast::Builtin::AntelopeDbFind => Builtin::AntelopeDbFind,
ast::Builtin::AntelopeDbGet => Builtin::AntelopeDbGet,
ast::Builtin::AntelopeDbNext => Builtin::AntelopeDbNext,
ast::Builtin::AntelopeDbLowerbound => Builtin::AntelopeDbLowerbound,
ast::Builtin::AntelopeLastPk => Builtin::AntelopeLastPk,
ast::Builtin::AntelopeDbIdx64Find => Builtin::AntelopeDbIdx64Find,
ast::Builtin::AntelopeDbIdx64Lowerbound => Builtin::AntelopeDbIdx64Lowerbound,
ast::Builtin::AntelopeDbIdx128Find => Builtin::AntelopeDbIdx128Find,
ast::Builtin::AntelopeDbIdx128Lowerbound => Builtin::AntelopeDbIdx128Lowerbound,
ast::Builtin::AntelopeDbIdx256Find => Builtin::AntelopeDbIdx256Find,
ast::Builtin::AntelopeDbIdx256Lowerbound => Builtin::AntelopeDbIdx256Lowerbound,
_ => panic!("Builtin should not be in the cfg"),
}
}
Expand Down
8 changes: 5 additions & 3 deletions src/codegen/revert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,8 @@ pub(super) fn assert_failure(
cfg: &mut ControlFlowGraph,
vartab: &mut Vartable,
) {
// On Solana, returning the encoded arguments has no effect
if ns.target == Target::Solana || ns.target == Target::Soroban {
// On Solana/Soroban/Antelope, returning the encoded arguments has no effect
if ns.target == Target::Solana || ns.target == Target::Soroban || ns.target == Target::Antelope {
cfg.add(vartab, Instr::AssertFailure { encoded_args: None });
return;
}
Expand Down Expand Up @@ -296,7 +296,9 @@ pub(super) fn require(
.copied()
.collect::<Vec<u8>>();

let to_print = if ns.target == Target::Soroban {
let to_print = if ns.target == Target::Soroban || ns.target == Target::Antelope {
// Soroban/Antelope: use a static string to avoid heap allocation
// (FormatString requires vector_new → __malloc → heap)
Expression::BytesLiteral {
loc: Codegen,
ty: Type::String,
Expand Down
Loading