#[public]Expand description
Just as with storage, Stylus SDK methods are Solidity ABI-equivalent. This means that contracts written in different programming languages are fully interoperable. You can even automatically export your Rust contract as a Solidity interface so that others can add it to their Solidity projects.
This macro makes methods “public” so that other contracts can call them by implementing the
Router trait.
#[public]
impl Contract {
// our owner method is now callable by other contracts
pub fn owner(&self) -> Result<Address, Vec<u8>> {
Ok(self.owner.get())
}
}
impl Contract {
// our set_owner method is not
pub fn set_owner(&mut self, new_owner: Address) -> Result<(), Vec<u8>> {
// ...
}
}In is example, [Vec<u8>] becomes the program’s revert data.
§#[payable]
As in Solidity, methods may accept ETH as call value.
#[public]
impl Contract {
#[payable]
pub fn credit(&mut self) -> Result<(), Vec<u8>> {
self.erc20.add_balance(msg::sender(), msg::value())
}
}In the above, msg::value is the amount of ETH passed to the contract in wei, which may
be used to pay for something depending on the contract’s business logic. Note that you have to
annotate the method with #[payable], or else calls to it will revert. This is
required as a safety measure to prevent users losing funds to methods that didn’t intend to
accept ether.
§Constructor
Constructors provide a standard way to deploy, activate, and initialize a stylus contract atomically. Without them, it isn’t possible to guarantee that the Stylus contract initialization code will be executed before other methods. When using Stylus constructors, cargo-stylus sends a transaction to a proxy-contract called StylusDeployer that performs all necessary steps.
The constructor function must be annotated with the #[constructor] attribute. It can have any
name, but it is advisable to call it constructor. There must be no constructor definition or a
single constructor for a contract. Like Solidity, function overloading for constructors is not
supported. Constructors may be annotated with the #[payable] attribute if they are
supposed to receive Ether.
The constructor must receive the self parameter, and it can have any number of other parameters.
The values for these parameters will be passed to the constructor when deploying the contract.
The constructor should return no value or a result value with a unit type and a vector of bytes
(Result<(), Vec<u8>>). If the constructor returns an error, the deployment will revert.
The SDK will ensure the constructor is called only once. To do so, it will wrap the constructor with a function that reads and writes to a specific slot in storage. When called, the constructor wrapper will check the contents of the specific slot and revert if it is different from zero. Then, after executing the constructor method, the wrapper will write a value to the specific slot, ensuring the next call to the constructor will revert.
#[public]
impl Contract {
#[constructor]
pub fn constructor(&mut self, owner: Address) -> Result<(), Vec<u8>> {
self.owner.set(owner);
Ok(())
}
}In the example above, the constructor receives a single parameter: the address of the contract’s
owner. Note that we shouldn’t use self.vm().msg_sender() because that will return the address
of StylusDeployer. Instead, we explicitly pass the address of the contract’s owner.
§pure view, and write
For non-payable methods the #[public] macro can figure state mutability out for you
based on the types of the arguments. Functions with &self will be considered view, those
with &mut self will be considered write, and those with neither will be considered pure.
Please note that pure and view functions may change the state of other contracts by calling
into them.
Please refer to the SDK Feature Overview for more information on defining methods.
§Exporting a Solidity interface
Recall that Stylus contracts are fully interoperable across all languages, including Solidity. The Stylus SDK provides tools for exporting a Solidity interface for your contract so that others can call it. This is usually done with the cargo stylus CLI tool.
The SDK does this automatically via a feature flag called export-abi that causes the
#[public] and #[entrypoint] macros to generate a main function
that prints the Solidity ABI to the console.
cargo run --features export-abi --target <triple>Note that because the above actually generates a main function that you need to run, the
target can’t be wasm32-unknown-unknown like normal. Instead you’ll need to pass in your target
triple, which cargo stylus figures out for you. This main function is also why the following
commonly appears in the main.rs file of Stylus contracts.
#![cfg_attr(not(feature = "export-abi"), no_main)]Here’s an example output. Observe that the method names change from Rust’s snake_case to
Solidity’s camelCase. For compatibility reasons, onchain method selectors default to
camelCase. You can customize the ABI name used for selector computation with #[selector(name = "...")].
Note too that you can use argument names like “address”
without fear. The SDK will prepend an _ when necessary.
interface Erc20 {
function name() external pure returns (string memory);
function balanceOf(address _address) external view returns (uint256);
}
interface Weth is Erc20 {
function mint() external payable;
function burn(uint256 amount) external;
}§Selector collision detection
The macro detects ABI selector collisions at compile time within a single #[public]
block, whether it annotates an impl block or a trait definition. If two methods produce
the same 4-byte selector (computed from the keccak256 hash of the Solidity function
signature string, e.g. transfer(uint256,address)), compilation will fail
with a descriptive error.
Collision checks are gated out when contract-client-gen is enabled, because that mode
generates call stubs for external contracts where selector routing is not emitted.
Not detected: cross-block collisions (across separate #[public] blocks) and
collisions with selectors inherited via #[implements(...)]. When using
#[implements], manually verify that your function selectors do not collide with
those of the inherited trait’s methods.