Contract Details

CN4TLsNtUYxveWJNa9d5mYHBbRsfyzDuBXm99C43yR8VQmv6W
0 FET
5
              //------------------------------------------------------------------------------
//
//   Copyright 2018-2019 Fetch.AI Limited
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.
//
//------------------------------------------------------------------------------

persistent sharded SWAP : StructuredData;
persistent MIGRATION_STATUS : StructuredData;

@init
function init(funding_wallet: Address)
    use MIGRATION_STATUS;
    var ms = StructuredData();

    set_FundingWalletAddr(ms, funding_wallet);
    set_SealedSwapId(ms, 0u64);
    set_EpochLength(ms, 64u64);
    set_NumberOfSuccessfulMigrationsSealed(ms, 0u64);
    set_NumberOfSuccessfulMigrationsEpoch(ms, 0u64);
    set_MigratedFETAmountSealed(ms, UInt256(0u64));
    set_MigratedFETAmountEpoch(ms, UInt256(0u64));

    MIGRATION_STATUS.set(ms);
endfunction


// TODO(py_api_array_support): Array<T> as input parameter is not supported on ledger-api-py side yet.
@action
function migrateMulti(swap_ids: Array<UInt64>, eth_tx_digests_b64: Array<String>)
    use MIGRATION_STATUS;
    var ms = MIGRATION_STATUS.get();

    var tx: Transaction = authoriseTx(ms);

    var transfers: Array<Transfer> = tx.transfers();

    assert(swap_ids.count() == transfers.count(), "Number of swap ids does not match number of transfers in Tx");
    assert(swap_ids.count() == eth_tx_digests_b64.count(), "Number of swap ids does not match number of ETH transaction digests");

    for (i in 0:swap_ids.count())
        migrateInternal(ms, swap_ids[i], transfers[i].to(), transfers[i].amount(), tx.digest(), eth_tx_digests_b64[i]);
    endfor
endfunction


@action
function migrate(swap_id: UInt64, eth_tx_digest_b64: String)
    use MIGRATION_STATUS;
    var ms = MIGRATION_STATUS.get();

    var tx: Transaction = authoriseTx(ms);
    var transfers: Array<Transfer> = tx.transfers();

    assert(transfers.count() == 1i32,
           "Unexpected number of transfers carried in Tx. This is single swap migration Tx, thus single transfer is expected.");

    migrateInternal(ms, swap_id, transfers[0].to(), transfers[0].amount(), tx.digest(), eth_tx_digest_b64);
endfunction


@action
function sealEpoch(new_epoch_swap_id: UInt64,
                   expected_number_of_successful_migrations_in_epoch: UInt64,
                   expected_migrated_FET_amount_in_epoch_b64: String)

    var buffer = Buffer(0i32);
    assert(buffer.fromBase64(expected_migrated_FET_amount_in_epoch_b64), "Input string is not Base64 encoded.");

    var ms: StructuredData = verifyEpochSeal(new_epoch_swap_id,
                                             expected_number_of_successful_migrations_in_epoch,
                                             UInt256(buffer));

    var tx: Transaction = authoriseTx(ms);

    assert(tx.transfers().count() == 0i32, "Explicit Seal Epoch transaction must not carry any native transfers");

    sealEpochInternal(ms);
endfunction


@query
function migrationStatus() : StructuredData
    use MIGRATION_STATUS;
    return MIGRATION_STATUS.get();
endfunction


@query
function querySwap(swap_id: UInt64) : StructuredData
    var swap_id_str = toString(swap_id);
    use SWAP [ swap_id_str ];
    var swap : StructuredData = SWAP.get(swap_id_str, null);
    return swap;
endfunction


function authoriseTx(migration_status: StructuredData) : Transaction
    var funding_wallet_addr: Address = get_FundingWalletAddr(migration_status);
    var transaction: Transaction = getContext().transaction();

    assert(transaction.from() == funding_wallet_addr, "Tx sender must be migration funding wallet.");

    return transaction;
endfunction


function verifyEpochBoundary(ms: StructuredData, swap_id: UInt64)
    var sealed_swapid = get_SealedSwapId(ms);
    var next_epoch_swapid = sealed_swapid + get_EpochLength(ms);

    assert((sealed_swapid <= swap_id) && (swap_id < next_epoch_swapid), "SwapId is out of epoch range");
endfunction


function verifyEpochSeal(new_epoch_swap_id: UInt64,
                         expected_number_of_successful_migrations_in_epoch: UInt64,
                         expected_migrated_FET_amount_in_epoch: UInt256) : StructuredData

    use MIGRATION_STATUS;
    var ms = MIGRATION_STATUS.get();

    var expected_new_epoch_swap_id = get_SealedSwapId(ms) + get_EpochLength(ms);
    var actual_number_of_successful_migrations_in_epoch = get_NumberOfSuccessfulMigrationsEpoch(ms);
    var actual_migrated_FET_amount_in_epoch = get_MigratedFETAmountEpoch(ms);

    assert(expected_new_epoch_swap_id == new_epoch_swap_id,
           "Unexpected swap_id " + toString(new_epoch_swap_id) + " for sealing new epoch, expected value is " +
             toString(expected_new_epoch_swap_id));

    assert(actual_number_of_successful_migrations_in_epoch == expected_number_of_successful_migrations_in_epoch,
           "Consistency check failed: Unexpected number of successful migrations in epoch " +
               toString(expected_number_of_successful_migrations_in_epoch) +
               " for sealing new epoch, expected value is " + toString(actual_number_of_successful_migrations_in_epoch));

    assert(actual_migrated_FET_amount_in_epoch == expected_migrated_FET_amount_in_epoch,
           "Consistency check failed: Unexpected amount of migrated FET in epoch " +
               toString(expected_number_of_successful_migrations_in_epoch) +
               " for sealing new epoch, expected amount is " + toString(actual_migrated_FET_amount_in_epoch));

    return ms;
endfunction


//TODO(ledger_structureddata_extension):
// Due to missing functionality in Fetch Ledger StructuredData type, this function takes ETH transaction
// digests as Base64 encoded strings rather than Buffer (or UInt256) type.
// Also the FET Address of the Funding Wallet is stored as display string (Base58 enc.) rather than directly Address type,
// as well as FET Tx digest is stored as HEX encoded string.
// These are temporary limitations until the StructuredData type will be extended with support for above mentioned types.
function createSwapStruct(fetch_dest_addr: Address,
                          amount: UInt64,
                          fet_tx_digest: UInt256,
                          eth_tx_digest_b64: String): StructuredData
    var swap = StructuredData();
    swap.set(key_SwapFetchAddressTo(), toString(fetch_dest_addr));
    swap.set(key_SwapFETAmount(), amount);
    swap.set(key_SwapFetchTxDigest(), fet_tx_digest);
    swap.set(key_SwapEthTxDigest(), eth_tx_digest_b64);
    return swap;
endfunction


function migrateInternal(ms: StructuredData,
                         swap_id: UInt64,
                         fetch_dest_addr: Address,
                         amount: UInt64,
                         fet_tx_digest: UInt256,
                         eth_tx_digest_b64: String)

    verifyEpochBoundary(ms, swap_id);
    recordNewSwap(ms, swap_id, fetch_dest_addr, amount, fet_tx_digest, eth_tx_digest_b64);
endfunction


function recordNewSwap(ms: StructuredData,
                       swap_id: UInt64,
                       fetch_dest_addr: Address,
                       amount: UInt64,
                       fet_tx_digest: UInt256,
                       eth_tx_digest_b64: String)

    var swap_id_str: String = toString(swap_id);

    assert(fetch_dest_addr != null, "Migration destination FET address is null reference");
    assert(toString(fetch_dest_addr) != "", "Migration destination FET address has invalid value.");

    //TODO(ledger_shardedstate_detect_preexistence): It is hard to detect previous existence of the data when
    // using ShardedState<T> and `T` type meets specific conditions:
    //  * when `T` is elementary type (integers ans floating point types) because value type does not have
    //    notion of `null`
    //  * when `T` is  reference type which does not implement comparison operator (such as StraucturedData type
    //    for example), instances of such reference types can not be compared against each other or `null`.
    // The `invalidStructuredData` below is just workaround for detecting pre-existence of the `StructuredData` type
    // value tailored for our specific usecase, where we can exploit the fact that String representation of the
    // valid `Address` type value can not be empty string:
    var invalidStructuredData = StructuredData();
    invalidStructuredData.set(key_SwapFetchAddressTo(), "");

    use SWAP [ swap_id_str ];
    var swap: StructuredData = SWAP.get(swap_id_str, invalidStructuredData);

    assert(swap.getString(key_SwapFetchAddressTo()) == "", "The swap id " + swap_id_str + " has been already processed.");

    var new_swap: StructuredData = createSwapStruct(fetch_dest_addr, amount, fet_tx_digest, eth_tx_digest_b64);
    SWAP.set(swap_id_str, new_swap);

    updateMigrationStatus(ms, amount);
endfunction


function updateMigrationStatus(ms: StructuredData, amount: UInt64)
    var sealed_swap_id = get_SealedSwapId(ms);
    var epoch_length = get_EpochLength(ms);

    var num_succ_sealed = get_NumberOfSuccessfulMigrationsSealed(ms);
    var num_succ_epoch = get_NumberOfSuccessfulMigrationsEpoch(ms);
    var migrated_fet_amount_sealed = get_MigratedFETAmountSealed(ms);
    var migrated_fet_amount_epoch = get_MigratedFETAmountEpoch(ms);

    num_succ_epoch += 1u64;
    migrated_fet_amount_epoch += UInt256(amount);

    if (num_succ_epoch == epoch_length)
        set_SealedSwapId(ms, sealed_swap_id + epoch_length);
        set_NumberOfSuccessfulMigrationsSealed(ms, num_succ_sealed + num_succ_epoch);
        set_MigratedFETAmountSealed(ms, migrated_fet_amount_sealed + migrated_fet_amount_epoch);

        set_NumberOfSuccessfulMigrationsEpoch(ms, 0u64);
        set_MigratedFETAmountEpoch(ms, UInt256(0u64));

        use MIGRATION_STATUS;
        MIGRATION_STATUS.set(ms);
        return;
    endif

    if (num_succ_epoch <= epoch_length)
        set_NumberOfSuccessfulMigrationsEpoch(ms, num_succ_epoch);
        set_MigratedFETAmountEpoch(ms, migrated_fet_amount_epoch);

        use MIGRATION_STATUS;
        MIGRATION_STATUS.set(ms);
        return;
    endif

    assert(false, "Number of successful swaps in epoch exceeds predefined epoch length.");
endfunction


function sealEpochInternal(ms: StructuredData)
    var sealed_swap_id = get_SealedSwapId(ms);
    var epoch_length = get_EpochLength(ms);

    var num_succ_sealed = get_NumberOfSuccessfulMigrationsSealed(ms);
    var num_succ_epoch = get_NumberOfSuccessfulMigrationsEpoch(ms);
    var migrated_fet_amount_sealed = get_MigratedFETAmountSealed(ms);
    var migrated_fet_amount_epoch = get_MigratedFETAmountEpoch(ms);

    set_SealedSwapId(ms, sealed_swap_id + epoch_length);
    set_NumberOfSuccessfulMigrationsSealed(ms, num_succ_sealed + num_succ_epoch);
    set_MigratedFETAmountSealed(ms, migrated_fet_amount_sealed + migrated_fet_amount_epoch);

    set_NumberOfSuccessfulMigrationsEpoch(ms, 0u64);
    set_MigratedFETAmountEpoch(ms, UInt256(0u64));

    use MIGRATION_STATUS;
    MIGRATION_STATUS.set(ms);
endfunction



function get_FundingWalletAddr(ms: StructuredData): Address
    var fwa_str: String = ms.getString(key_FundingWalletAddr());
    return Address(fwa_str);
endfunction

function get_EpochLength(ms: StructuredData): UInt64
    return ms.getUInt64(key_EpochLength());
endfunction

function get_SealedSwapId(ms: StructuredData): UInt64
    return ms.getUInt64(key_SealedSwapId());
endfunction

function get_NumberOfSuccessfulMigrationsSealed(ms: StructuredData): UInt64
    return ms.getUInt64(key_NumberOfSuccessfulMigrationsSealed());
endfunction

function get_NumberOfSuccessfulMigrationsEpoch(ms: StructuredData): UInt64
    return ms.getUInt64(key_NumberOfSuccessfulMigrationsEpoch());
endfunction

function get_MigratedFETAmountSealed(ms: StructuredData): UInt256
    return ms.getUInt256(key_MigratedFETAmountSealed());
endfunction

function get_MigratedFETAmountEpoch(ms: StructuredData): UInt256
    return ms.getUInt256(key_MigratedFETAmountEpoch());
endfunction


function set_FundingWalletAddr(ms: StructuredData, value: Address)
    var fwa_str = toString(value);
    ms.set(key_FundingWalletAddr(), fwa_str);
endfunction

function set_EpochLength(ms: StructuredData, value: UInt64)
    ms.set(key_EpochLength(), value);
endfunction

function set_SealedSwapId(ms:StructuredData, value: UInt64)
    ms.set(key_SealedSwapId(), value);
endfunction

function set_NumberOfSuccessfulMigrationsSealed(ms: StructuredData, value: UInt64)
    ms.set(key_NumberOfSuccessfulMigrationsSealed(), value);
endfunction

function set_NumberOfSuccessfulMigrationsEpoch(ms: StructuredData, value: UInt64)
    ms.set(key_NumberOfSuccessfulMigrationsEpoch(), value);
endfunction

function set_MigratedFETAmountSealed(ms: StructuredData, value: UInt256)
    ms.set(key_MigratedFETAmountSealed(), value);
endfunction

function set_MigratedFETAmountEpoch(ms: StructuredData, value: UInt256)
    ms.set(key_MigratedFETAmountEpoch(), value);
endfunction



function key_FundingWalletAddr(): String
    return "wallet";
endfunction

function key_EpochLength(): String
    return "epochLen";
endfunction

function key_SealedSwapId(): String
    return "sealedSwpId";
endfunction

function key_NumberOfSuccessfulMigrationsSealed(): String
    return "numSuccMigrationsSealed";
endfunction

function key_NumberOfSuccessfulMigrationsEpoch(): String
    return "numSuccMigrationsEpoch";
endfunction

function  key_MigratedFETAmountSealed(): String
    return "migFETAmountSealed";
endfunction

function  key_MigratedFETAmountEpoch(): String
    return "migFETAmountEpoch";
endfunction


function  key_SwapFetchAddressTo() : String
    return "address";
endfunction

function  key_SwapFETAmount() : String
    return "amount";
endfunction

function  key_SwapFetchTxDigest() : String
    return "fetTxDigest";
endfunction

function  key_SwapEthTxDigest() : String
    return "ethTxDigest";
endfunction