Skip to content
This repository has been archived by the owner on Aug 1, 2023. It is now read-only.

Polygon support #330

Merged
merged 1 commit into from
Jul 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ethereum-client/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ pub fn decode_event(topics: Vec<String>, data: String) -> Result<EthereumEvent,
.iter()
.map(|topic| decode_topic(topic).ok_or(EventError::InvalidTopic))
.collect::<Result<Vec<ethabi::Hash>, _>>()?;

match topic_hashes.first().ok_or(EventError::InvalidTopic)? {
t if *t == *LOCK_EVENT_TOPIC => {
let log: ethabi::Log = LOCK_EVENT
Expand Down
32 changes: 23 additions & 9 deletions ethereum/contracts/Starport.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ contract Starport {

address immutable public admin;
address constant public ETH_ADDRESS = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
bytes4 constant MAGIC_HEADER = "ETH:";
string constant ETH_CHAIN = "ETH";
bytes4 immutable chainIdHeader; // = "ETH:" on Ethereum
bytes32 immutable chainId; // = "ETH" on Ethereum
address[] public authorities;
mapping(address => uint) public supplyCaps;

Expand All @@ -35,9 +35,23 @@ contract Starport {
event ExecuteProposal(string title, bytes[] extrinsics);
event NewSupplyCap(address indexed asset, uint supplyCap);

constructor(ICash cash_, address admin_) {
constructor(ICash cash_, address admin_, bytes32 chainId_, bytes4 chainIdHeader_) {
cash = cash_;
admin = admin_;
chainId = chainId_;
chainIdHeader = chainIdHeader_;
}

function bytes32ToString(bytes32 _bytes32) public pure returns (string memory) {
uint8 i = 0;
while (i < 32 && _bytes32[i] != 0) {
i++;
}
bytes memory bytesArray = new bytes(i);
for (i = 0; i < 32 && _bytes32[i] != 0; i++) {
bytesArray[i] = _bytes32[i];
}
return string(bytesArray);
}

/**
Expand All @@ -51,7 +65,7 @@ contract Starport {
* @param asset The asset to lock in the Starport
*/
function lock(uint amount, address asset) external {
lockTo(amount, asset, ETH_CHAIN, toBytes32(msg.sender));
lockTo(amount, asset, bytes32ToString(chainId), toBytes32(msg.sender));
}

/**
Expand All @@ -77,7 +91,7 @@ contract Starport {
* @dev Use `lock` to lock CASH or collateral assets.
*/
function lockEth() public payable {
lockEthTo(ETH_CHAIN, toBytes32(msg.sender));
lockEthTo(bytes32ToString(chainId), toBytes32(msg.sender));
}

/*
Expand Down Expand Up @@ -234,10 +248,10 @@ contract Starport {
isNoticeInvoked[noticeHash] = true;

require(notice.length >= 100, "Must have full header"); // 4 + 3 * 32
require(notice[0] == MAGIC_HEADER[0], "Invalid header[0]");
require(notice[1] == MAGIC_HEADER[1], "Invalid header[1]");
require(notice[2] == MAGIC_HEADER[2], "Invalid header[2]");
require(notice[3] == MAGIC_HEADER[3], "Invalid header[3]");
require(notice[0] == chainIdHeader[0], "Invalid header[0]");
require(notice[1] == chainIdHeader[1], "Invalid header[1]");
require(notice[2] == chainIdHeader[2], "Invalid header[2]");
require(notice[3] == chainIdHeader[3], "Invalid header[3]");

(uint noticeEraId, uint noticeEraIndex, bytes32 noticeParent) =
abi.decode(notice[4:100], (uint, uint, bytes32));
Expand Down
2 changes: 1 addition & 1 deletion ethereum/contracts/test/StandardToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ contract FaucetToken is StandardToken {
* @title Fee ERC20 token
*/
contract FeeToken is BaseToken {
uint256 constant FEE_BPS = 500; // 50%
uint256 constant FEE_BPS = 500; // 5%

constructor(uint256 _initialAmount, string memory _tokenName, uint8 _decimalUnits, string memory _tokenSymbol) BaseToken(_initialAmount, _tokenName, _decimalUnits, _tokenSymbol) {
}
Expand Down
2 changes: 1 addition & 1 deletion ethereum/contracts/test/StarportHarness.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ contract StarportHarness is Starport {

uint public counter;

constructor(ICash cash_, address admin_) Starport(cash_, admin_) {
constructor(ICash cash_, address admin_) Starport(cash_, admin_, "ETH", "ETH:") {
}

/// Harness to call `getQuorum`
Expand Down
2 changes: 1 addition & 1 deletion integration/__tests__/cash_scen.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ buildScenarios('Cash Scenarios', cash_scen_info, [
},
{
name: 'Collateral Borrowed Interest Lump Sum',
scenario: async ({ ashley, bert, chuck, cash, chain, usdc, zrx }) => {
scenario: async ({ bert, chuck, cash, chain, usdc, zrx }) => {
await chain.setFixedRate(usdc, 500); // 5% APY fixed
await bert.lock(1000000, zrx);
await bert.transfer(1000, usdc, chuck);
Expand Down
3 changes: 2 additions & 1 deletion integration/__tests__/lock_scen.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ let lock_scen_info = {
tokens: [
{ token: 'usdc', balances: { ashley: 1000 } }
],
validators: ['alice', 'bob']
validators: ['alice', 'bob'],
native: true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need native here, but I'm not strictly against it, either. Probably should just use the default though, nah?

Copy link
Contributor Author

@waynenilsen waynenilsen Jun 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was before we were doing wasm logging was impossible to tell what was going on, also is native faster?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH, it doesn't really matter here. Native is when you're asking for a different genesis native, e.g. running m14's native build. It's irrelevant here, but also, again, if we wanted to change it, we should change the default, not one one-off scen.

};

async function getCash({ ashley, usdc, cash, chain, starport }) {
Expand Down
101 changes: 94 additions & 7 deletions integration/__tests__/matic_scen.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,118 @@
const { buildScenarios } = require('../util/scenario');
const { getNotice } = require('../util/substrate');

let now = Date.now();

let lock_scen_info = {
tokens: [
{ token: 'usdc', balances: { ashley: 1000 } },
{ token: 'maticZrx', balances: { darlene: 1000 } }
{ token: 'maticZrx', balances: { darlene: 1000000 } },
{ token: 'zrx', balances: { bert: 1000000 } },
{ token: 'comp' }
],
validators: ['alice', 'bob'],
actors: ['ashley', 'darlene'],
actors: ['ashley', 'bert', 'chuck', 'darlene', 'edward'],
chain_opts: {
matic: {
name: 'matic',
provider: 'ganache', // [env=PROVIDER]
provider: 'ganache',
ganache: {
opts: {},
web3_port: null
},
},
},
native: true, // for log messages - removes wasm:stripped - no need for wasm no forkless upgrade testing here
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is more for freeze time here. You should have log messages so long as you run yarn build which handles the wasm stripped concerns already.

freeze_time: now,
initial_yield: 300,
initial_yield_start_ms: now
};

buildScenarios('Matic', lock_scen_info, [
{
skip: true, // TODO FIX SCEN
name: 'Matic',
scenario: async ({ darlene, maticZrx }) => {
await darlene.lock(1000, maticZrx);
name: 'Matic Lock',
scenario: async ({ashley, usdc, darlene, maticZrx }) => {
// Lock on Polygon
let balance, liquidity;
await darlene.lock(100, maticZrx);
balance = await darlene.balanceForToken(maticZrx);
let darleneLiquidity = await darlene.liquidity();
expect(balance).toEqual(100);
expect(darleneLiquidity).toBeGreaterThan(0);

// lock on ethereum
await ashley.lock(1000, usdc);
balance = await ashley.balanceForToken(usdc);
liquidity = await ashley.liquidity();
expect(balance).toEqual(1000);
expect(liquidity).toBeGreaterThan(0);

// lock on polygon again
await darlene.lock(100, maticZrx);
balance = await darlene.balanceForToken(maticZrx);
liquidity = await darlene.liquidity();
expect(balance).toEqual(200); // double the asset balance (no interest, 0 utilization)
expect(liquidity).toEqual(2*darleneLiquidity); // liquidity must have gone up
}
},
{
name: 'Collateral Borrowed Interest Lump Sum',
scenario: async ({ darlene, chuck, cash, chain, usdc, maticZrx}) => {
// await prices.postPrices();
let balance, liquidity;
await chain.setFixedRate(usdc, 500); // 5% APY fixed
await darlene.lock(10000, maticZrx);
balance = await darlene.balanceForToken(maticZrx);
let liquidity1 = await darlene.liquidity();
expect(balance).toEqual(10000);
expect(liquidity1).toBeGreaterThan(0);
// now we know everything is in order, make the transfer, let's say half our liquidity worth
await darlene.transfer(1000, usdc, chuck);
expect(await darlene.chainBalance(usdc)).toEqual(-1000);
expect(await chuck.chainBalance(usdc)).toEqual(1000);
expect(await darlene.chainBalance(cash)).toBeCloseTo(-0.01, 2); // 1¢ transfer fee
expect(await chuck.chainBalance(cash)).toEqual(0);
await chain.accelerateTime({years: 1});
expect(await darlene.chainBalance(usdc)).toEqual(-1000);
expect(await chuck.chainBalance(usdc)).toEqual(1000);
expect(await darlene.chainBalance(cash)).toBeCloseTo(-51.53272669767585, 3); // -50 * Math.exp(0.03) - 0.01
expect(await chuck.chainBalance(cash)).toBeCloseTo(51.52272669767585, 3); // 50 * Math.exp(0.03)
}
},
{
name: "Extract Collateral",
scenario: async ({ darlene, maticZrx, chain, maticStarport }) => {
let assetInfo = await maticZrx.getAssetInfo();

let balance, liquidity;
// start with 1,000,000
// lock 10,000
// on matic = 990,000
// on gateway = 10,000
await darlene.lock(10000, maticZrx);
balance = await darlene.balanceForToken(maticZrx);
expect(balance).toEqual(10000); // gateway balance not yet debited
expect(await darlene.tokenBalance(maticZrx)).toEqual(990000); // matic balance not yet credited
liquidity = await darlene.liquidity();
expect(liquidity).toBeGreaterThan(0);

// request extract 50 maticZrx
let extract = await darlene.extract(50, maticZrx);
let notice = getNotice(extract);
let signatures = await chain.getNoticeSignatures(notice);

// before we submit - query the balances to ensure they are as expected
balance = await darlene.balanceForToken(maticZrx);
expect(balance).toEqual(9950); // gateway balance debited
expect(await darlene.tokenBalance(maticZrx)).toEqual(990000); // matic balance not yet credited
// now invoke the notice!
await maticStarport.invoke(notice, signatures);
// balances are updated everywhere, should now be
// on matic = 990,050
// on gateway = 9,950
balance = await darlene.balanceForToken(maticZrx);
expect(balance).toEqual(9950); // gateway balance debited
expect(await darlene.tokenBalance(maticZrx)).toEqual(990050); // matic balance now credited
}
},
]);
43 changes: 31 additions & 12 deletions integration/util/scenario/actor.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class Actor {
}

chain() {
return this.ctx.chains.find(this.chainName());
return this.ctx.chains.find(this.chainName);
}

show() {
Expand All @@ -32,11 +32,13 @@ class Actor {
}

toChainAccount() {
return { Eth: this.ethAddress() };
const returnValue = {};
returnValue[this.chain().nameAsPascalCase()] = this.ethAddress();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this might be localChainAddress() instead of ethAddress(), possibly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think for now having ethAddress is ok but we will ultimately change a lot of this code when flow comes into play. To be honest, I'm a little bit intimidated refactoring something as pervasive as that could be in js without the help of a compiler

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cmd+Shift+F and JavaScript are sadly good friends.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, we could use TypeScript, but it's a huge burden for tests since it takes a lot more time to write and tests are pretty one-offy. We could technically upgrade if we think it would help us.

return returnValue;
}

toTrxArg() {
return `Eth:${this.ethAddress()}`;
return `${this.chain().nameAsPascalCase()}:${this.ethAddress()}`;
}

declareInfo() {
Expand All @@ -59,14 +61,16 @@ class Actor {
}

async sign(data) {
return await this.ctx.eth.sign(data, this);
return await this.chain().sign(data, this);
}

async signWithNonce(data) {
let currentNonce = await this.nonce();
let signature = await this.sign(`${currentNonce}:${data}`);
const signatureData = {};
signatureData[this.chain().nameAsPascalCase()] = [this.ethAddress(), signature];

return [ { Eth: [ this.ethAddress(), signature ] }, currentNonce ];
return [ signatureData, currentNonce ];
}

async runTrxRequest(trxReq) {
Expand All @@ -77,7 +81,7 @@ class Actor {
}

async ethBalance() {
return await this.ctx.eth.ethBalance(this);
return await this.chain().ethBalance(this);
}

async tokenBalance(tokenLookup) {
Expand Down Expand Up @@ -138,19 +142,28 @@ class Actor {
return Number(balance) / 1e6;
}

async liquidityForToken(token) {
async balanceForToken(token) {
let assetBalance = await this.ctx.getApi().query.cash.assetBalances(token.toChainAsset(), this.toChainAccount());
if (assetBalance === 0) {
return 0;
}

return token.toTokenAmount(assetBalance.toBigInt());
}

async liquidityForToken(token) {
let assetBalance = await this.balanceForToken(token);
let price = await token.getPrice();
let liquidityFactor = await token.getLiquidityFactor();

if (assetBalance == 0) {
if (assetBalance === 0) {
return 0;
} else if (assetBalance > 0) {
// AssetBalance • LiquidityFactor_Asset • Price_Asset
return token.toTokenAmount(assetBalance.toBigInt()) * price * liquidityFactor;
return assetBalance * price * liquidityFactor;
} else {
// AssetBalance ÷ LiquidityFactor_Asset • Price_Asset
return token.toTokenAmount(assetBalance.toBigInt()) * price / liquidityFactor;
return assetBalance * price / liquidityFactor;
}
}

Expand All @@ -167,10 +180,12 @@ class Actor {
};

return await this.declare("lock", [amount, asset], async () => {
let tx = await asset.chain().starport.lock(this, amount, asset);
const chain = asset.chain();
let tx = await chain.starport.lock(this, amount, asset);

let event;
if (opts.awaitEvent) {
event = await this.ctx.chain.waitForEthProcessEvent('cash', asset.lockEventName());
event = await this.ctx.chain.waitForL1ProcessEvent(chain, 'cash', asset.lockEventName());
}
return {
event,
Expand Down Expand Up @@ -284,6 +299,10 @@ function actorInfoMap(keyring) {
darlene: {
chainName: 'matic',
key_uri: '//Darlene'
},
edward: {
chainName: 'matic',
key_uri: '//Edward'
}
};
}
Expand Down
5 changes: 5 additions & 0 deletions integration/util/scenario/cash_token.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ class CashToken extends Token {
return await this.cashToken.methods.symbol().call();
}

async getLiquidityFactor() {
return 1;
}


async upgrade(impl, upgradeCall = null) {
if (upgradeCall) {
await this.proxyAdmin.methods.upgradeAndCall(
Expand Down
Loading