이 문서에서는 CW-20 토큰을 Injective 발행 네이티브 토큰(TokenFactory 모듈 사용)으로 교환하거나 그 반대로 교환할 수 있는 CW20 Adapter Contract를 설명합니다. CW-20 Adapter GitHub 저장소는 여기 를 참조하세요.
CW-20은 CosmWasm에서 대체 가능한 토큰에 대한 사양으로, ERC-20 사양을 기반으로 합니다. CosmWasm 내에서 임의의 대체 가능한 토큰을 생성하고 처리할 수 있으며, 해당 토큰을 생성, 발행, 소각 및 계정 간 전송하는 방법을 지정합니다. adapter 컨트랙트는 승인된 소스 CW-20 컨트랙트만 토큰을 발행할 수 있도록 하여 “위조” 토큰 생성을 방지합니다.
CW-20 표준은 상대적으로 성숙하고 완전하지만, 토큰은 순전히 CosmWasm 컨텍스트 내에 존재하며 발행 컨트랙트에 의해 완전히 관리됩니다(계정 잔액 추적 포함). 이는 Injective의 네이티브 모듈과 직접 상호작용할 수 없음을 의미합니다(예: Injective exchange 모듈을 통해 거래하거나 발행 컨트랙트를 포함하지 않고 전송하는 것이 불가능).
위의 사항을 고려하여 CW20과 Injective bank 모듈 간의 브릿지 역할을 하는 솔루션을 제공해야 합니다.
컨트랙트의 워크플로우는 다음과 같습니다:
새 CW-20 토큰 등록
X CW-20 토큰을 Y TokenFactory 토큰으로 교환 (원래 CW-20 토큰은 컨트랙트에 보관됨)
Y TF 토큰을 X CW-20 토큰으로 다시 교환 (CW-20 토큰이 릴리스되고 TokenFactory 토큰이 소각됨)
메시지
RegisterCw20Contract { addr: Addr }
adapter에서 처리할 새 CW-20 컨트랙트(addr)를 등록하고 factory/{adapter_contract}/{cw20_contract} 형식의 새 TokenFactory 토큰을 생성합니다.
ExecuteMsg :: RegisterCw20Contract { addr } => execute_register :: handle_register_msg ( deps , env , info , addr )
pub fn handle_register_msg (
deps : DepsMut < InjectiveQueryWrapper >,
env : Env ,
info : MessageInfo ,
addr : Addr ,
) -> Result < Response < InjectiveMsgWrapper >, ContractError > {
if is_contract_registered ( & deps , & addr ) {
return Err ( ContractError :: ContractAlreadyRegistered );
}
let required_funds = query_denom_creation_fee ( & deps . querier) ? ;
if info . funds . len () > required_funds . len () {
return Err ( ContractError :: SuperfluousFundsProvided );
}
let mut provided_funds = info . funds . iter ();
for required_coin in & required_funds {
let pf = provided_funds
. find ( | c | -> bool { c . denom == required_coin . denom })
. ok_or ( ContractError :: NotEnoughBalanceToPayDenomCreationFee ) ? ;
match pf . amount . cmp ( & required_coin . amount) {
Ordering :: Greater => return Err ( ContractError :: SuperfluousFundsProvided ),
Ordering :: Less => return Err ( ContractError :: NotEnoughBalanceToPayDenomCreationFee ),
Ordering :: Equal => {}
}
}
let create_denom_msg = register_contract_and_get_message ( deps , & env , & addr ) ? ;
Ok ( Response :: new () . add_message ( create_denom_msg ))
}
Receive { sender: String, amount: Uint128, msg: Binary }
Receiver CW-20 인터페이스의 구현입니다.
CW-20 컨트랙트에 의해서만 호출되어야 합니다.
ExecuteMsg :: Receive { sender , amount , msg : _ } => execute_receive :: handle_on_received_cw20_funds_msg ( deps , env , info , sender , amount )
pub fn handle_on_received_cw20_funds_msg (
deps : DepsMut < InjectiveQueryWrapper >,
env : Env ,
info : MessageInfo ,
recipient : String ,
amount : Uint128 ,
) -> Result < Response < InjectiveMsgWrapper >, ContractError > {
if ! info . funds . is_empty () {
return Err ( ContractError :: SuperfluousFundsProvided );
}
let mut response = Response :: new ();
let token_contract = info . sender;
if ! is_contract_registered ( & deps , & token_contract ) {
ensure_sufficient_create_denom_balance ( & deps , & env ) ? ;
response = response . add_message ( register_contract_and_get_message ( deps , & env , & token_contract ) ? );
}
let master = env . contract . address;
let denom = get_denom ( & master , & token_contract );
let coins_to_mint = Coin :: new ( amount . u128 (), denom );
let mint_tf_tokens_message = create_mint_tokens_msg ( master , coins_to_mint , recipient );
Ok ( response . add_message ( mint_tf_tokens_message ))
}
RedeemAndTransfer \{ recipient: Option\<String\> \}
첨부된 TokenFactory 토큰을 상환하고 CW-20 토큰을 수신자에게 전송합니다. 수신자가 제공되지 않으면 메시지 발신자에게 전송됩니다.
RedeemAndSend { recipient: String, submessage: Binary }
첨부된 TokenFactory 토큰을 상환하고 CW-20 토큰을 수신자 컨트랙트에 전송합니다. 호출자는 선택적 하위 메시지를 제공할 수 있습니다.
ExecuteMsg :: RedeemAndTransfer { recipient } => execute_redeem :: handle_redeem_msg ( deps , env , info , recipient , None )
ExecuteMsg :: RedeemAndSend { recipient , submsg } => execute_redeem :: handle_redeem_msg ( deps , env , info , Some ( recipient ), Some ( submsg ))
pub fn handle_redeem_msg (
deps : DepsMut < InjectiveQueryWrapper >,
env : Env ,
info : MessageInfo ,
recipient : Option < String >,
submessage : Option < Binary >,
) -> Result < Response < InjectiveMsgWrapper >, ContractError > {
let recipient = recipient . unwrap_or_else ( || info . sender . to_string ());
if info . funds . len () > 1 {
return Err ( ContractError :: SuperfluousFundsProvided );
}
let tokens_to_exchange = info
. funds
. iter ()
. find_map ( | c | -> Option < AdapterCoin > {
match AdapterDenom :: new ( & c . denom) {
Ok ( denom ) => Some ( AdapterCoin { amount : c . amount, denom }),
Err ( _ ) => None ,
}
})
. ok_or ( ContractError :: NoRegisteredTokensProvided ) ? ;
let cw20_addr = tokens_to_exchange . denom . cw20_addr . clone ();
let is_contract_registered = CW20_CONTRACTS . contains ( deps . storage, & tokens_to_exchange . denom . cw20_addr);
if ! is_contract_registered {
return Err ( ContractError :: NoRegisteredTokensProvided );
}
let burn_tf_tokens_message = create_burn_tokens_msg ( env . contract . address, tokens_to_exchange . as_coin ());
let cw20_message : WasmMsg = match submessage {
None => WasmMsg :: Execute {
contract_addr : cw20_addr ,
msg : to_binary ( & Cw20ExecuteMsg :: Transfer {
recipient ,
amount : tokens_to_exchange . amount,
}) ? ,
funds : vec! [],
},
Some ( msg ) => WasmMsg :: Execute {
contract_addr : cw20_addr ,
msg : to_binary ( & Cw20ExecuteMsg :: Send {
contract : recipient ,
amount : tokens_to_exchange . amount,
msg ,
}) ? ,
funds : vec! [],
},
};
Ok ( Response :: new () . add_message ( cw20_message ) . add_message ( burn_tf_tokens_message ))
}
CW-20 주소(등록된 경우)의 메타데이터를 쿼리하고 bank 모듈에서 setMetadata를 호출합니다(TokenFactory 액세스 메서드 사용).
ExecuteMsg :: UpdateMetadata { addr } => execute_metadata :: handle_update_metadata ( deps , env , addr )
pub fn handle_update_metadata (
deps : DepsMut < InjectiveQueryWrapper >,
env : Env ,
cw20_addr : Addr ,
) -> Result < Response < InjectiveMsgWrapper >, ContractError > {
let is_contract_registered = CW20_CONTRACTS . contains ( deps . storage, cw20_addr . as_str ());
if ! is_contract_registered {
return Err ( ContractError :: ContractNotRegistered );
}
let token_metadata = fetch_cw20_metadata ( & deps , cw20_addr . as_str ()) ? ;
let denom = get_denom ( & env . contract . address, & cw20_addr );
let set_metadata_message = create_set_token_metadata_msg ( denom , token_metadata . name, token_metadata . symbol, token_metadata . decimals);
Ok ( Response :: new () . add_message ( set_metadata_message ))
}
쿼리
RegisteredContracts {}
등록된 CW - 20 컨트랙트 목록을 반환합니다 .
QueryMsg :: RegisteredContracts {} => to_binary ( & query :: registered_contracts ( deps ) ? )
pub fn registered_contracts ( deps : Deps < InjectiveQueryWrapper >) -> StdResult < Vec < Addr >> {}
NewDenomFee {}
새 tokenFactory denom을 등록하는 데 필요한 수수료를 반환합니다 .
QueryMsg :: NewDenomFee {} => to_binary ( & query :: new_denom_fee ( deps ) ? )
pub fn new_denom_fee ( deps : Deps < InjectiveQueryWrapper >) -> StdResult < Uint128 > {}