메인 콘텐츠로 건너뛰기
이 문서에서는 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))
}

UpdateMetadata { addr : Addr}

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> {}