Giter Site home page Giter Site logo

superform-core's Introduction

Overview

codecov

The Superform Protocol is a suite of non-upgradeable, non-custodial smart contracts that act as a central repository for yield and a router for users. It is modular, permissionless to list vaults, and enables intent-based transactions across chains that allow users to execute into an arbitrary number of tokens, chains, and vaults at once.

For DeFi protocols, it acts as an instant out-of-the-box distribution platform for ERC4626-compliant vaults. For users, it allows access to any vault listed on the platform from the chain and asset of their choice in a single transaction.

Core capabilities for protocols include:

  • Permissionlessly list your vaults on Superform by adding your ERC4626 vault to the proper 'Form' (a vault adapter within Superform).
  • Create a profile page with embeddable data sources for users to find more information about your protocol
  • Manage metadata for yield opportunities
  • Users can deposit into your vaults from any chain without the need to deploy your vaults on that chain

Core capabilities for users include:

  • Deposit or withdraw into any vault using any asset from any chain
  • Batch desired actions across multiple vaults and multiple chains in a single transaction
  • Automate and mange your yield portfolio from any chain
  • Automatically compound your yield position
  • Make cross-chain transactions using multiple AMBs

This repository includes all Superform contracts and can be split into two categories: Core and Periphery.

  • Core contracts contain logic to move liquidity and data across chains along with maintaining roles in the protocol
  • Periphery contracts contain the main touch-points for protocols and users to interface with and include helper contracts to ease 3rd party integrations
Superform__SuperformProtocol--DARK (2)

Resources

Project structure

.
├── script
├── security-review
├── src
  ├── crosschain-data
  ├── crosschain-liquidity
  ├── forms
  ├── interfaces
  ├── libraries
  ├── payments
  ├── settings
  ├── types
  ├── vendor
├── test
├── foundry.toml
└── README.md
  • script contains deployment and utility scripts and outputs /script
  • security-review contains information relevant to prior security reviews and the scope of bug bounties/security-review
  • src is the source folder for all smart contract code/src
    • crosschain-data implements the sending of messages from chain to chain via various AMBs /src/crosschain-data
    • crosschain-liquidity implements the movement of tokens from chain to chain via bridge aggregators /src/crosschain-liquidity
    • forms implements types of yield that can be supported on Superform and introduces a queue when they are paused /src/forms
    • interfaces define interactions with other contracts /src/interfaces
    • libraries define functions used across other contracts and error states in the protocol /src/libraries
    • payments implements the handling and processing of payments for cross-chain actions /src/payments
    • settings define, set, and manage roles in the Superform ecosystem /src/settings
    • types define core data structures used in the protocol /src/types
    • vendor is where all externally written interfaces reside /src/vendor
  • test contains tests for contracts in src /test

Documentation

We recommend visiting technical documentation at https://docs.superform.xyz.

Contract Architecture

  1. All external actions, except Superform creation, start in SuperformRouter.sol. For each deposit or withdraw function the user has to provide the appropriate "StateRequest" found in DataTypes.sol
  2. All deposit and withdrawal actions can be to single or multiple destinations, single or multi vaults, and same-chain or cross-chain. Any token can be deposited from any chain into a vault with swapping and bridging handled in a single call. Sometimes it is also needed to perform another action on the destination chain for tokens with low bridge liquidity, through the usage of DstSwapper.sol. Similarly for withdraw actions, users can choose to receive a different token than the one redeemed for from the vault, but funds must go back directly to the user (i.e. no use of DstSwapper.sol).
  3. Any individual tx must be of a specific kind, either all deposits or all withdraws, for all vaults and destinations
  4. Vaults themselves can be added permissionlessly to Forms in SuperformFactory.sol by calling createSuperform(). Forms are code implementations that adapt to the needs of a given vault, currently all around the ERC-4626 Standard. Any user can wrap a vault into a Superform using the SuperformFactory but only the protocol may add new Form implementations.
  5. This wrapping action leads to the creation of Superforms which are assigned a unique id, made up of the superForm address, formId, and chainId.
  6. Users are minted SuperPositions on successful deposits, a type of ERC1155 modified called ERC1155A. On withdrawals these are burned. Users may also within each "StateRequest" deposit choose whether to retain4626 which sends the vault share directly to the user instead of holding in the appropriate Superform, but only SuperPositions can be withdrawn through SuperformRouter.

User Flow

In this section we will run through examples where users deposit and withdraw into vault(s) using Superform.

Screenshot 2023-11-24 at 9 47 38 AM

Same-chain Deposit Flow

Screenshot 2023-11-24 at 9 48 01 AM
  • Validation of the input data in SuperformRouter.sol.
  • Process swap transaction data if provided to allow SuperformRouter to move tokens from the user to the Superform and call directDepositIntoVault to move tokens from the Superform into the vault.
  • Store ERC-4626 shares in the Superform and mint the apppropriate amount of SuperPositions back to the user.

Cross-chain Deposit Flow

Screenshot 2023-11-24 at 9 48 37 AM
  • Validation of the input data in SuperformRouter.sol.
  • Dispatch the input token to the liquidity bridge using an implementation of a BridgeValidator.sol and LiquidityHandler.sol.
  • Create an AMBMessage with the information about what is going to be deposited and by whom.
  • Message the information about the deposits to the vaults using CoreStateRegistry.sol. This is done with the combination of a main AMB and a configurable number of proof AMBs for added security, a measure set via setRequiredMessagingQuorum in SuperRegistry.sol.
  • Forward remaining payment to PayMaster.sol to cover the costs of cross-chain transactions and relayer payments.
  • Receive the information on the destination chain's CoreStateRegistry.sol. Assuming no swap was required in DstSwapper.sol, at this step, assuming both the payload and proof have arrived, a keeper updates the messaged amounts to-be deposited with the actual amounts received through the liquidity bridge using updateDepositPayload. The maximum number it can be updated to is what the user specified in StateReq.amount. If the end number of tokens received is below the minimum bound of what the user specified, calculated by StateReq.amount*(10000-StateReq.maxSlippage), the deposit is marked as failed and must be rescued through the rescueFailedDeposit function to return funds back to the user through an optimisic dispute process.
  • The keeper can then process the received message using processPayload. Here the deposit action is try-catched for errors. Should the action pass, a message is sent back to source acknowledging the action and mints SuperPositions to the user. If the action fails, no message is sent back, no SuperPositions are minted, and the rescueFailedDeposit function must be used.

Same-chain Withdrawal Flow

Screenshot 2023-11-24 at 9 48 55 AM
  • Validation of the input data in SuperformRouter.sol.
  • Burn the corresponding SuperPositions owned by the user and call directWithdrawFromVault in the Superform, which redeems funds from the vault.
  • Process transaction data (either a swap or a bridge) if provided and send funds back to the user.

Cross-chain Withdrawal Flow

  • Validation of the input data in SuperformRouter.sol.
  • Burn the corresponding SuperPositions owned by the user in accordance to the input data.
  • Create the AMBMessage with the information about what is going to be withdrawn and by whom.
  • Message the information about the withdrawals from the vaults using CoreStateRegistry.sol. This is done with the combination of a main AMB and a configurable number of proof AMBs for added security, a measure set via setRequiredMessagingQuorum in SuperRegistry.sol.
  • Forward remaining payment to PayMaster.sol to cover the costs of cross-chain transactions and relayer payments.
  • If no transaction data was provided with the transaction, but the user defined an intended token and chain to recieve assets back on, assuming both the payload and proof have arrived, a keeper can call updateWithdrawPayload to update the payload with transaction data. This can be done to reduce the chance of transaction data failure due to latency.
  • The keeper can then process the received message using processPayload. Here the withdraw action is try-catched for errors. Should the action pass, the underlying obtained is bridged back to the user in the form of the desired tokens to be received. If the action fails, a message is sent back indicating that SuperPositions need to be re-minted for the user according to the original amounts that were burned. No rescue methods are implemented given the re-minting behavior on withdrawals.

Tests

Step by step instructions on setting up the project and running it

  1. Make sure Foundry is installed

  2. Set the .env variables in .env.example using your nodes

POLYGON_RPC_URL=
AVALANCHE_RPC_URL=
FANTOM_RPC_URL=
BSC_RPC_URL=
ARBITRUM_RPC_URL=
OPTIMISM_RPC_URL=
ETHEREUM_RPC_URL=
  1. Install submodules and dependencies:
foundryup
forge install
  1. Run forge test to run tests against the contracts
$ forge test

superform-core's People

Contributors

0xtimepunk avatar renovate[bot] avatar shortdoom avatar sidduhere avatar smitrajput avatar ssomraaj avatar subhasishgoswami avatar sujithsomraaj avatar vikramarun avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

superform-core's Issues

LayerZeroImplementation.sol

Referring to this:

https://github.com/superform-xyz/superform-core/blob/develop/src/crosschain-data/adapters/layerzero/LayerzeroImplementation.sol

Additional questions:

For the chainids, layerzero uses our own custom chain ids instead of conventional ones https://layerzero.gitbook.io/docs/technical-reference/mainnet/supported-chain-ids

Please investigate if there’s a need to implementation translation of this in your contract to accommodate other parts of your system as your receive function relies on the layerzero chain id.

Use the latest version of solidity-examples package. Do not copy contracts from LayerZero repositories directly to your project.
I recommend inheriting from solidity-example instead of downloading our contracts.

If your project requires token bridging inherit your token from OFT or ONFT. For new tokens use OFT or ONFT, for bridging existing tokens use ProxyOFT or ProxyONFT.
N/A

For bridging only between EVM chains use OFT and for bridging between EVM and non EVM chains (e.g., Aptos) use OFTV2.
N/A

Do not hardcode LayerZero chain Ids. Use admin restricted setters instead.
Chain id is set by admin setters.
If your contract derives from LzApp, do not call lzEndpoint.send directly, use _lzSend.
_lzSend was called.

Do not hardcode address zero (address(0)) as zroPaymentAddress when estimating fees and sending messages. Pass it as a parameter instead.
This was hard coded, is better to be passed in as param.
address zroPaymentAddress_
Do not hardcode useZro to false when estimating fees and sending messages. Pass it as a parameter instead.
Same as above.
Do not hardcode zero bytes (bytes(0)) as adapterParamers. Pass them as a parameter instead.
This was good, passed in as extdata
Make sure to test the amount of gas required for the execution on the destination. Use custom adapter parameters and specify minimum destination gas for each cross-chain path when the default amount of gas (200,000) is not enough. This requires whoever calls the send function to provide the adapter params with a destination gas >= amount set in the minDstGasLookup for that chain. So that your users don't run into failed messages on the destination. It makes it a smoother end-to-end experience for all.

Needs to be tested when live.
Do not add requires statements that repeat existing checks in the parent contracts. For example, lzReceive function in LzApp contract checks that the message sender is LayerZero endpoint and the scrAddress is a trusted remote, do not perform the same checks in nonblockingLzReceive.
Correct implementation from our template.

For ONFTs that allow minting a range of tokens on each chain, make the variables that specify the range (e.g. startMintId and endMintId) immutable.
N/A

PaymentHelper.sol

PaymentHelper.sol is sitting at 29.286 kB.

Same general feedback on reducing bytecode size here.

You have a ton of functions that do single and multi actions. Worth considering removing some of them and adjusting upstream logic appropriately.

SuperPositions.sol

SuperPositions.sol is 36.966 kB according to forge build --sizes so I'd like to offer some feedback to reduce this contract's size as well.

modifier onlyMinter(uint256 superformId) {

modifier onlyBatchMinter(uint256[] memory superformIds) {

Consider only having one modifier that can handle 1 to many checks here.


function stateMultiSync(AMBMessage memory data_) external override returns (uint64 srcChainId_) {

function stateSync(AMBMessage memory data_) external override returns (uint64 srcChainId_) {

These 2 functions are very very similar. Can we just have one of them that handles single and multi sync?

foundry.toml

You can increase your optimizer runs that is set in foundry.toml

Generally anywhere around 30,000 would be good. There's a diminishing return generally.

payments

PaymentHelper.sol

srcAmount += ambFees;
if (isDeposit_) {
/// @dev step 2: estimate update cost (only for deposit)
totalDstGas += _estimateUpdateCost(req_.dstChainIds[i], superformIdsLen);
/// @dev step 3: estimation processing cost of acknowledgement
/// @notice optimistically estimating. (Ideal case scenario: no failed deposits / withdrawals)
srcAmount += _estimateAckProcessingCost(len, superformIdsLen);
/// @dev step 4: estimate liq amount
liqAmount += _estimateLiqAmount(req_.superformsData[i].liqRequests);
/// @dev step 5: estimate dst swap cost if it exists
totalDstGas += _estimateSwapFees(req_.dstChainIds[i], req_.superformsData[i].hasDstSwaps);
}
/// @dev step 6: estimate execution costs in dst (withdraw / deposit)
/// note: execution cost includes acknowledgement messaging cost
totalDstGas += _estimateDstExecutionCost(isDeposit_, req_.dstChainIds[i], superformIdsLen);
/// @dev step 7: convert all dst gas estimates to src chain estimate (withdraw / deposit)
dstAmount += _convertToNativeFee(req_.dstChainIds[i], totalDstGas);

Uncheck all of this addition if possible


totalAmount = srcAmount + dstAmount + liqAmount;

Here too


function estimateMultiDstSingleVault(

Same feedback for this function


function estimateSingleXChainMultiVault(

Same feedback for ths function. Uncheck where necessary


/// @dev step 2: estimate update cost (only for deposit)
if (isDeposit_) totalDstGas += _estimateUpdateCost(req_.dstChainId, superformIdsLen);
/// @dev step 3: estimate execution costs in dst
/// note: execution cost includes acknowledgement messaging cost
totalDstGas += _estimateDstExecutionCost(isDeposit_, req_.dstChainId, superformIdsLen);
/// @dev step 4: estimation execution cost of acknowledgement
if (isDeposit_) srcAmount += _estimateAckProcessingCost(1, superformIdsLen);
/// @dev step 5: estimate liq amount
if (isDeposit_) liqAmount += _estimateLiqAmount(req_.superformsData.liqRequests);
/// @dev step 6: estimate if swap costs are involved
if (isDeposit_) totalDstGas += _estimateSwapFees(req_.dstChainId, req_.superformsData.hasDstSwaps);

All of this addition and function calls can just be combined under a single if (isDeposit_) right? No need to evaluate isDeposit_ over and over.

if (isDeposit_) {
    do it all here
}

function estimateSingleXChainSingleVault(

Same advice as above in this function. Unchecked math and condense under single if (isDeposit_)


function estimateSingleDirectSingleVault(

Unchecked math here to save gas


function estimateSingleDirectMultiVault(

Same advice here


function estimateAMBFees(

Same advice here


totalFees += tempFee;

Same advice here


totalFees += tempFee;
feeSplitUp[i] = tempFee;

And here


liqAmount += req_[i].nativeAmount;

And here

Abstract Router Functions to support single SuperForm/SingleAmount per Request

Some idea to explore - the only two things passed as array with potentially more than 1 element are amounts and superFormIds. Which also leads to the above FIXME note. If we are already splitting into direct and xchain functions, can we abstract implementation of singleDirect/Xchain even further so that here we wouldn't need to return multiple formIds. Pre-validating if user deposits into single superFormIds or multiple. Part of a clean-up task for this function.

Originally posted by @shortdoom in #18 (comment)

CoreStateRegistry.sol

As you indicated, you were interested in some advice as to how you can reduce the size of the bytecode of the CoreStateRegistry.sol contract.

You have 3 cases where you have a single and a multi version of the same function.

if (isMulti != 0) {
(prevPayloadBody, finalState) = _updateMultiDeposit(payloadId_, prevPayloadBody, finalAmounts_);
} else {
(prevPayloadBody, finalState) = _updateSingleDeposit(payloadId_, prevPayloadBody, finalAmounts_[0]);
}

_updateMultiDeposit
_updateSingleDeposit

if (txType == uint8(TransactionType.WITHDRAW)) {
returnMessage = isMulti == 1
? _multiWithdrawal(payloadId_, payloadBody_, srcSender, srcChainId)
: _singleWithdrawal(payloadId_, payloadBody_, srcSender, srcChainId);

_multiWithdrawal
_singleWithdrawal

returnMessage = isMulti == 1
? _multiDeposit(payloadId_, payloadBody_, srcSender, srcChainId)
: _singleDeposit(payloadId_, payloadBody_, srcSender, srcChainId);

_multiDeposit
_singleDeposit

In these 3 cases, I would recommend removing the single version of each of these. You can rename the functions for better clarity to:

_updateDeposit
_withdrawal
_deposit

These functions should just loop as they normally do, even if it's only looping over 1 item.

You'll cut down the bytecode size significantly as the single functions that you'd be removing are pretty large to begin with.

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

This repository currently has no open or pending branches.

Detected dependencies

github-actions
.github/workflows/CI.yml
  • actions/checkout v4
  • foundry-rs/foundry-toolchain v1
  • actions/cache v4
  • actions/checkout v4
  • foundry-rs/foundry-toolchain v1
  • actions/cache v4
  • actions/checkout v4
  • foundry-rs/foundry-toolchain v1
  • codecov/codecov-action v4.0.0-beta.3
  • actions/checkout v4
  • foundry-rs/foundry-toolchain v1

  • Check this box to trigger a request for Renovate to run again on this repository

Integrate Linea Network

i wanted to deposit with Linea network, but i noticed it's not among the available networks. Linea Network is one of the chains with the most dapp integrations on ethereum. Many web3 users are very familiar with linea network and the recent Linea upgrade brought about a huge reduction in gas fees with enhanced security, making it much of better option for wide adoption.

crosschain-data

HyperlaneImplementation.sol

Is there any input validation required here? I know the admin sets it, but maybe want some?


igp.payForGas{ value: msg.value }(
messageId, domain, extraData_.length > 0 ? abi.decode(extraData_, (uint256)) : 0, srcSender_
);

I believe splitting this outside of a ternary expression would be marginally cheaper


(,,, uint8 registryId,,) = decoded.txInfo.decodeTxInfo();

Since you're only unpacking registryId it may be worth having a slightly modified version of decodeTxInfo that ONLY returns registryId.

Albeit not a TON of extra gas, but you're still paying for the compute of the bitshifts of all of the values that you're not unpacking here:

function decodeTxInfo(uint256 txInfo_)
internal
pure
returns (uint8 txType, uint8 callbackType, uint8 multi, uint8 registryId, address srcSender, uint64 srcChainId)
{
txType = uint8(txInfo_);
callbackType = uint8(txInfo_ >> 8);
multi = uint8(txInfo_ >> 16);
registryId = uint8(txInfo_ >> 24);
srcSender = address(uint160(txInfo_ >> 32));
srcChainId = uint64(txInfo_ >> 192);
}


LayerZeroImplementation.sol

(,,, uint8 registryId,,) = decoded.txInfo.decodeTxInfo();

Same thing that I mentioned in the Hyperlane contract.


if (
!(
srcAddress_.length == trustedRemote.length && keccak256(srcAddress_) == keccak256(trustedRemote)
&& trustedRemote.length > 0
)
) {
revert Error.INVALID_SRC_SENDER();
}

Chained conditional reverts cost marginally more than individual revert statements.

You also technically have an extra conditional here that you can remove. You can remove the length quality checks and use the other 2 checks.


WormholeARImplementation.sol

(,,, uint8 registryId,,) = decoded.txInfo.decodeTxInfo();

Once again, same thing here.


CoreStateRegistry.sol

isMulti == 1
? ISuperPositions(_getAddress(keccak256("SUPER_POSITIONS"))).stateMultiSync(message_)
: ISuperPositions(_getAddress(keccak256("SUPER_POSITIONS"))).stateSync(message_);

Believe splitting this and not using a ternary will save a few gas. Not the biggest deal though


? _multiWithdrawal(payloadId_, payloadBody_, srcSender, srcChainId)
: _singleWithdrawal(payloadId_, payloadBody_, srcSender, srcChainId);

Same thing here.


? _multiDeposit(payloadId_, payloadBody_, srcSender, srcChainId)
: _singleDeposit(payloadId_, payloadBody_, srcSender, srcChainId);

And here.


(,, uint8 multi,, address srcSender,) = DataLib.decodeTxInfo(payloadHeader[payloadId_]);

Spending a bit more gas than needed here.


failedDeposits[payloadId_].refundAddress = refundAddress == address(0) ? srcSender : refundAddress;

Same as above


for (uint256 i; i < arrLen;) {
if (finalAmounts_[i] == 0) {
revert Error.ZERO_AMOUNT();
}
/// @dev observe not consuming the second return value
(multiVaultData.amounts[i],, validLen) = _updateAmount(
dstSwapper,
multiVaultData.hasDstSwaps[i],
payloadId_,
i,
finalAmounts_[i],
multiVaultData.superformIds[i],
multiVaultData.amounts[i],
multiVaultData.maxSlippages[i],
finalState_,
validLen
);
unchecked {
++i;
}
}
if (validLen > 0) {
uint256[] memory finalSuperformIds = new uint256[](validLen);
uint256[] memory finalAmounts = new uint256[](validLen);
uint256[] memory maxSlippage = new uint256[](validLen);
bool[] memory hasDstSwaps = new bool[](validLen);
uint256 currLen;
for (uint256 i; i < arrLen;) {
if (multiVaultData.amounts[i] != 0) {
finalSuperformIds[currLen] = multiVaultData.superformIds[i];
finalAmounts[currLen] = multiVaultData.amounts[i];
maxSlippage[currLen] = multiVaultData.maxSlippages[i];
hasDstSwaps[currLen] = multiVaultData.hasDstSwaps[i];
unchecked {
++currLen;
}
}
unchecked {
++i;
}
}

Would recommend architecting this in a way where you don't need two loops. Happy to dive deeper if you'd like.


if (IBaseForm(superform).getStateRegistryId() == _getStateRegistryId(address(this))) {

You can call _getStateRegistry(address(this)) once outside of the loop and cache that result rather than calling it in every loop iteration.


address superformFactory = superRegistry.getAddress(keccak256("SUPERFORM_FACTORY"));

Cache as an instance of ISuperformFactory instead here so you're not typecasting in every loop iteration


try IBaseForm(superform_).xChainWithdrawFromVault(

Same thing here. Cache as an instance of IBaseForm


TimelockStateRegistry.sol

Assuming you can uncheck this increment here


if (p.status != TimelockStatus.PENDING) {
revert Error.INVALID_PAYLOAD_STATUS();
}
if (p.lockedTill > block.timestamp) {
revert Error.LOCKED();

Can move these slightly up (before the bridgeValidator line)


(, uint256 callbackType,,,, uint64 srcChainId) = _payloadHeader.decodeTxInfo();

Same advice as above


BroadcastRegistry.sol

Can probably uncheck this increment

src

SuperformFactory.sol

mapping(uint32 formImplementationId => bool paused) public formImplementationPaused;

If these forms are paused and unpaused frequently, you can save gas by instead using mapping (uint32 => uint8)

If you use 1 and 2 to denote false and true. This way you're never resetting the slot and then initializing a cold storage slot should you use bools or uint denoted
by 0 and 1


SuperPositions.sol

for (uint256 i; i < len; ++i) {

Uncheck increment i


txHistory[payloadId_] = txInfo_;

Does this need any input validation?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.