Learnings from Solidity, opportunities in Starknet
# WHY
We are building a collaboration protocol.
The center piece is the concept of bounty.
A bounty represents a task you can work on and be paid for.
The protocol is focused on the bounty lifecycle.
It needs to be modular in order to adapt to every bounty business need.
Solidity
What we explored to achieve modularity, main takeaways and decisions made
Cairo
What is really different in the end? What are the opportunities?
Getting modular again
What we have thought of, what did we achieve, and the next steps
# AGENDA
# GOAL
A protocol user must be able to easily swap technical implementations of a part or the whole of the contract business logic
Contracts must be easy to interact with, whether it is from another contract or a Dapp frontend
The protocol must be easy to build on, using the highest amount of standards β EIP or good practices β is a must-have
interface ICandidateApprovalStrategy {
function approveCandidate(address) external;
}
contract Bounty {
function approveCandidate(address candidate_) external {
candidateApprovalStrategy.approveCandidate(candidate_);
}
}
β ABI is explicit β Strategies can be made stateless with call delegation, and shared among bounties
β Strategies do not have flexibility over function signatures
β Strategies must hold all bounty variable declarations if use through call delegation
Caller -> Bounty -> Strategy
COMPOSABILITYβ
DISCOVERABILITY
READABILITY OF THIS SLIDE
# OPTION 1
interface ICandidateApprovalStrategy {
}
contract RewardingCandidateApprovalStrategy is ICandidateApprovalStrategy {
function approveCandidate(address bounty_, address candidate_, address erc20_) external {
}
}
β Strategy is free to expose its own function signatures
β Strategy has to store its own data about bounties
β Caller has to know all strategy addresses for a given bounty
Caller -> Strategy -> Bounty
DISCOVERABILITY
COMPOSABILITY
# OPTION 2
# OPTION 3
A unique register where you store "the interface implementer for this address is at this address"
interface ICandidateApprovalStrategy {
}
contract Bounty {
function constructor(address candidateApprovalStrat_) {
erc1820.registerImplementerFor("ICandidateApprovalStrategy", candidateApprovalStrat_);
}
}
Caller -> ERC1820Registry -> Strategy -> Bounty
DISCOVERABILITY*
COMPOSABILITY
*The ABI problem still needs to be addressed but is simple to solve with tooling
β
# CAIRO
β°οΈ Inheritance
β°οΈ InterfaceId
π€ Call delegation
π€ Function selectors
# OPPORTUNITY
You can use delegate call to invoke a function that changes a storage variable which wasnβt defined in the calling contract. In such a case, the new corresponding storage variable will be created in the calling contract [...]
π€―
π€―
π€―
Call
delegation
# PROPOSITION
π« Strategy pattern
π« Call delegation
π« EIP1820~ish
π« Fallback function
# Bounty.cairo
@contract_interface
namespace IStrategy:
func init(strategy_address : felt):
end
end
@storage_var
func bounty_strategy_routing(function_selector : felt) -> (strategy : felt):
end
@constructor
func constructor(candidate_approval_strategy : felt):
IStrategy.delegate_init(
contract_address=application_strategy, strategy_address=candidate_approval_strategy)
return ()
end
# -----------------
# RewardCandidateApproval.cairo
@storage_var
func bounty_strategy_routing(function_selector : felt) -> (strategy : felt):
end
@external
func init(strategy_address : felt):
bounty_strategy_routing.write(APPROVE_CANDIDATE_SELECTOR, strategy_address)
return ()
end
@external
func approve_candidate(candidate: felt):
end
# Bounty.cairo
[...]
@external
@raw_input
@raw_output
func __default__(selector : felt, calldata_size : felt, calldata : felt*) -> (
retdata_size : felt, retdata : felt*):
let (strategy) = bounty_strategy_routing.read(selector)
assert_not_zero(strategy)
let (retdata_size : felt, retdata : felt*) = delegate_call(
contract_address=strategy,
function_selector=selector,
calldata_size=calldata_size,
calldata=calldata)
return (retdata_size=retdata_size, retdata=retdata)
end
# -----------------
# RewardCandidateApproval.cairo
[...]
@external
func approve_candidate(candidate: felt):
end
# THE END
β Composability
Every strategy can have their own function signatures
β Discoverability
The bounty ABI is the concatenation of all strategies' ABIs
π§ Limitations
Strategies all share the same state β the bounty's β so you might audit the code before making a choice for your bounty platform
β Interoperability
This pattern is very similar to what's described in EIP2535 β the Diamond pattern β next step is to be fully compatible
@bernanstard
Β
@onlydust_xyz