---
# System prepended metadata

title: xDAI Bridge USDS migration proposal v1
tags: [Gnosis Chain]

---

# Background

https://forum.gnosis.io/t/gip-118-should-sdai-be-replaced-by-susds-in-the-bridge/9354https://forum.gnosis.io/t/maker-migration-impact-on-gnosis-chain/8993/1https://forum.sky.money/t/usds-and-puredai-the-two-paths-for-decentralized-stablecoins/24987https://forum.sky.money/t/first-wave-integrator-program/24784

related: [Research - Impact and edge-cases or issues for moving from DAI to USDS and Savings USDS on the bridge](https://www.notion.so/Research-Impact-and-edge-cases-or-issues-for-moving-from-DAI-to-USDS-and-Savings-USDS-on-the-bridg-1203b92f9cd0804e800bc421e603c24f?pvs=21) 

# Proposal 1

1. USDS is the default token on the xDAI bridge, instead of DAI.
    1. All the parameters i.e. dailyLimit, executeDailyLimit, etc reflect the value for USDS.
    2. Users get USDS by default when they claim back the token by calling `executeSignatures`
2. Both USDS and DAI can be deposited into xDAI bridge, but the bridge will convert DAI into USDS within the same `relayTokens` function call.
3. Users have to swap USDS back to DAI if they want to have DAI back (on UI level or calling `DaiUsds` contract)

## Deposit Flow

1. Deposit USDS: works the same as previous DAI deposit, but USDS is transferred to the bridge instead.
2. Deposit DAI: DAI is first transferred to the bridge and swap to USDS. This will add an extra [~140k](https://etherscan.io/tx/0xb144ea7ea7da824137fe689482c2dba9d7d34f7d69a8e4862cb66dd84e721a61) gas on top the original relayTokens ([~61k](https://etherscan.io/tx/0x52721e5ee6824c0835b0113e12d72e5a713dce5d2f2a3eac251944b24a13729f)) call, in total ~200k gas cost.
3. Condition check: 
    1. Since `relayTokens` function interface remains the same, the bridge need a way to check whether user deposits DAI or USDS. It will check `allowance` of USDS or DAI for the bridge, and the following graph explain the condition logic.
    2. Alternative: Specify the token address in `relayTokens` instead of `allowance` check
        1. New function interface: `relayTokens(token, receiver, amount)`
![USDSupgrade-Deposit flow](https://hackmd.io/_uploads/Bkgfihix-l.png)
![USDSupgrade-Relay Condition](https://hackmd.io/_uploads/r1rMi3jlWl.png)



- `relayTokens(address _receiver, uint256 _amount)`
    
    ```jsx
    function relayTokens(address _receiver, uint256 _amount){
    		uint256 usdsAllowance = USDS.allowance(msg.sender, address(this);
    		uint256 daiAllowance = DAI.allowance(msg.sender, address(this);
    		
    		if(usdsAllowance >= _amount){
    			USDS.transferFrom(msg.sender, address(this), _amount);
    		}else if(daiAllowance >= _amount){
    			DAI.transferFrom(msg.sender, address(this), _amount);
    		}else{
    			revert InsufficientAllowance();
    		} 
    }
    ```
    

## Invest Flow

1. Bridge Upgrade: During the bridge upgrade, all the current sDAI hold by the bridge will be converted to sUSDS by calling Spark’s `MigrationActions` contract.
2. Invest USDS: By calling `investUSDS()` in xDAI bridge contract, USDS from the bridge will be deposited into sUSDS contract and hold sUSDS in return.
3. Refill Bridge: When the amount of USDS is below minCashThreshold, anyone can refill the bridge, and sUSDS will be withdrawn from sUSDS contract to USDS.
4. Pay Interest: Checks the USDS interest amount that can be relayed to Gnosis Chain and relays to interest receiver(controlled by Karpatkey) on Gnosis Chain.

![USDSupgrade-InvestUSDS](https://hackmd.io/_uploads/Bkp7j2olWe.png)


## ForeignXDAIBridge

```jsx
  function swapSDAIToSUSDS() public view returns(uint256){
        // swap all sDAI into sUSDS, only one time during the bridge upgrade
        bytes32 isUSDSBridgeUpgrade = keccak256("upgrade_DAI_to_USDS");
        require(!boolStorage[isUSDSBridgeUpgrade], "USDS bridge ugprade completed");
        uint256 maxDAIAsset = sDaiToken().maxWithdraw(address(daiToken()));

        require(maxDAIAsset > 0, "No sDAI to swap");
        // params: receiver, assetIn
        IMigrationActions(0xf86141a5657Cf52AEB3E30eBccA5Ad3a8f714B89).migrateSDAIAssetsToSUSDS(address(this),maxDAIAsset);
        boolStorage[isUSDSBridgeUpgrade]  = true;
    }

```

## Withdraw Flow

1. Claim Token: User calls `executeSignatures` and get USDS back.
2. Swap USDS to DAI: If usesr wants to get DAI instead, they have to call DaiToUsds contract or swap on Bridge UI.

![USDSupgrade-Withdraw flow](https://hackmd.io/_uploads/r1lSsnsebx.png)


## Impact

1. Contract level on Ethereum:
    1. Function interface: `relayTokens` , `executeSignatures` remains the same. 
        1. Contract value:
            1. erc20Token is now USDS instead of DAI 🔴
            2. the bridge parameters are indicating the value for USDS i.e. dailyLimit, executionDailyLimit, maxPerTx, etc.
2. Contract level on Gnosis Chain: No impact
3. Validator level: No impact
4. Application level on Ethereum:
    1. Receive USDS and need to swap back to DAI manually (option on Bridge UI).
    2. DeFi? Need feedback ⚠️

## Implementation Phases

1. One time bridge upgrade

## Risk & Dependency

1. Depend on `DaiUsds` contract to swap USDS↔DAI, and the xDAI bridge don’t keep both tokens but only USDS.
    1. Risk from USDS upgradeable contract
        1. The `DaiUsds` contract created by Sky is non-upgradeable, and can only be used for swapping DAI ↔ USDS.  (in `Vat`, `can[DaiUsds][DaiJoin] = true` and `can[DaiUsds][UsdsJoin] = true` ) . However,  USDS owner can call `deny` function to revoke the `mint` ability for `UsdsJoin` .
        2. Can we create our own `DaiUsds` contract? 
            1. Still subject to upgradeability risk of USDS contract. 
2. Depend on Spark’s MigrationActions contract to convert sDAI to sUSDS. 

# Proposal 2

What changes?

1. Reserve a minimum DAI amount in the bridge, and only invest extra DAI + USDS into sUSDS
2. In the case where swap DAI↔USDS is not available, the min DAI amount in bridge can be claimed by bridge owner.

**Deposit Flow**

![USDSupgrade-Deposit flow - Proposal 2.drawio](https://hackmd.io/_uploads/HJFLsnogWx.png)

**Invest Flow**

Same as Proposal 1

**Withdraw Flow**

Same as Proposal 1

## Impact

1. Same as Proposal 1, but new `minDaiThreshold` storage variable is introduced on xDAI bridge on Ethereum.

## Implementation Phases

1. One time bridge upgrade

## Risk & Dependency

1. During the emergency state (swap DAI ↔ USDS is not feasible), calling `XDaiBridge.claimTokens(DAI, to) onlyIfUpgradeabilityOwner` can claim back the `DAI.balanceOf(XDaiBridge)` (min DAI threshold) to `to` address.

# Proposal 3

1. Router contract to route relay token function to xDAI bridge or Omnibridge, or others in future upgrade. 
    1. Router contract is controlled by bridge governors and upgradeable
    2. Routes:
        1. DAI → Peripheral
        2. USDS → XDaiBridge
        3. Other tokens → Omnibridge
2. xDAI bridge only accept deposit from USDS token.

## Deposit Flow

1. User interact with Router contract and specify the token address: `relayTokens(token, receiver, amount`
2. When token = DAI, router contract calls Peripheral contract to swap Dai To USDS and call xDAI bridge relayToken.
3. When token = USDS, router contract calls XDAI bridge directly.
4. When token = others, router contract calls Omnibridge.

![USDSupgrade-Router-ExecuteSignatures.drawio](https://hackmd.io/_uploads/BJnPjnixWe.png)

## Invest flow

Same as proposal 1

## Withdraw Flow

1. User calls Router contract to claim their token back:
    1. When user calls `executeSignatures` :
        1. USDS is transferred to receiver in xDAI bridge case
        2. relevant token is transferred to receiver in Omnibridge case
    2. If user wants to receiver, calls `executeSignaturesAndSwapToDai`:
        1. Router calls Peripheral to swap USDS to DAI and transfers back to receiver.
        2. Or calling `executeSignatures` and swap USDS to DAI on UI.


![USDSupgrade-Router-ExecuteSignatures.drawio](https://hackmd.io/_uploads/Bk9_ohixZx.png)

## BridgeRouter contract

```jsx
pragma solidity ^0.8.0;

contract BridgeRouter {

    mapping(address => address) public tokenRoutes;

    function relayTokens(address _token, address _receiver, uint256 _amount) external {
        address route = tokenRoutes[_token];
        if (_token == DAI || _token == USDS){
            IXdaiBridgePeripheral(route).relayTokens(_receiver, _amount);
        }else{
            IOmnibridge(route).relayTokens(_token, _receiver, _amount);
        } 
    }

    // DAI -> DAIPeripheral
    // USDS -> Xdaibridge
    // Other tokens -> Omnibridge
    function setRoute(address token, address route) public onlyOwner{
        require(route!=address(0) && route!=address(this));
        tokenRoutes[token] = route;
    }

    function executeSignatures(bytes memory data, bytes memory signatures) external {
        // check the length of data
        if(data.length == 104){
            // xdai bridge
           IForeignBridge(FOREIGN_XDAIBRIDGE).executeSignatures(data, signatures);
        }else{
            // amb & omnibridge
           IForeignBridge(FOREIGN_AMB).safeExecuteSignaturesWithAutoGasLimit(data, signatures);
        }
    }

    function executeSignaturesAndSwapToDai(bytes memory data, bytes memory signatures, bytes memory permitSignatures) external{
        require(data.length == 104, "Invalid data length");
        address xdaiBridgePeripheral = tokenRoutes[DAI];
        IXdaiBridgePeripheral(xdaiBridgePeripheral).executeSignaturesAndSwapToDai(msg.sender, data, signatures, permitSignatures);
    }
    

}
```

## Peripheral contract

```jsx
pragma solidity ^0.8.0;

contract XDaiBridgePeripheral{

    address router;

    function relayTokens(address receiver, uint256 amount) onlyRouter external{
        // swap Dai to Usds
        IDaiUsds(DAIUSDS).daiToUsds(receiver, amount);

        // call XDaibridge relayTokens
        IUSDS(USDS).approve(FOREIGN_XDAIBRIDGE, amount);

        IXdaiBridge(FOREIGN_XDAIBRIDGE).relayTokens(receiver,amount);
    }

    function executeSignaturesAndSwapToDai(address owner, bytes memory data, bytes memory signatures, bytes memory permitSignatures) onlyRouter external{

        IXdaiBridge(FOREIGN_XDAIBRIDGE).executeSignatures(data, signatures);
        address recipient;
        uint256 amount;
        bytes32 nonce;
        address contractAddress;
        assembly {
            recipient := mload(add(data, 20))
            amount := mload(add(data, 52))
            nonce := mload(add(data, 84))
            contractAddress := mload(add(data, 104))
        }
        IUSDS(USDS).permit(owner, address(this), amount, permitSignatures);
        IUSDS(USDS).transferFrom(owner, address(this), amount);
        IDaiUsds(DAIUSDS).usdsToDai(owner, amount);

    }

}
```

## Impact

1. Contract level on Ethereum:
    1. Function interface: `relayTokens` , `executeSignatures` remains the same. 
        1. Contract value:
            1. erc20Token is now USDS instead of DAI 🔴
            2. the bridge parameters indicate the value for USDS i.e. dailyLimit, executionDailyLimit, maxPerTx, etc.
    2. New `BridgeRouter` and `XdaiBridgePeripheral` contracts
2. Contract level on Gnosis Chain: No impact
3. Validator level: No impact
4. Application level on Ethereum:
    1.  `BridgeRouter` contract can be used as an entry point to reduce complexity of interacting with two different bridges (XDai bridge & Omnibridge)
    2. Increase of gas compares to interacting with the bridge directly.

## Risk & Dependency

1. Same as Proposal 1 for the DaiUsds and Sparks contracts dependency.
2. `BridgeRouter` and `XdaiBridgePeripheral` should be managed by Gnosis team.

# Open question

1. Rename all DAI related function in USDS or remain the same? How much effort does the other code (i.e. Keeper from Karpatkey) need to be changed?
2. Should Router contract implement a claim function for user who accidentally `transfer` token into the bridgeRouter contract?

# Interest Rate comparison

|  | **Invested Amount** | **Interest Received** | **Diff** |
| --- | --- | --- | --- |
| **Current bridge setup** | 9.32E+25 | 1.07E+25 |  |
| **Proposal 1 & 3** | 9.32E+25 | 1.16E+25 | + 9.32E+23 (as compare to current)
+ 1.25E+23 (as compare to P2) |
| **Proposal 2** | 9.22E+25 | 1.15E+25 | + 8.07E+23 (as compare to current)
- 1.25E+23 (as compare to P1) |

Based on data January 15, 2025 

| SSR | 12.50% |
| --- | --- |
| DSR | 11.50% |
| DAI on xDAI bridge | 9.41865E+25 |
| min DAI threshold | [1E+24](https://etherscan.io/unitconverter?wei=1000000000000000000000000) |
| min USDS threshold | [1E+24](https://etherscan.io/unitconverter?wei=1000000000000000000000000) |
| DAI Decimal | 18 |

## Reference

- Contracts
    1. Spark MigrationActions: https://etherscan.io/address/0xf86141a5657Cf52AEB3E30eBccA5Ad3a8f714B89
    2. DaiUsds: https://etherscan.io/address/0x3225737a9bbb6473cb4a45b7244aca2befdb276a
    3. USDS: https://etherscan.io/address/0xdc035d45d973e3ec169d2276ddab16f1e407384f
    4. sUSDS token: https://etherscan.io/address/0xa3931d71877c0e7a3148cb7eb4463524fec27fbd
    5. sDAI: https://etherscan.io/address/0x83F20F44975D03b1b09e64809B757c47f942BEeA
    6. DAI: https://etherscan.io/address/0x6b175474e89094c44da98b954eedeac495271d0f

- Issue with using Spark’s MigrationActions contract
    
    ```jsx
     IMigrationActions(MigrationActions).migrateSDAIAssetsToSUSDS(address(this), maxDAIAsset);
     investedAmount(DAI) still remains the same after swapping, and has to be removed by calling disableInterest
     however, it will create accounting issue because sDAI is already swapped with sDAI.withdraw and can't perform twice in disableInterset() call
    
    ```