tl;dr Existe um bug crítico em muitos contratos de tokens ERC20 que surgiram a partir de uma atualização recente do Solidity.
Noções Básicas do ERC
O padrão ERC20 é de longe o padrão de token mais comum na plataforma Ethereum. O ERC20 é definido como uma interface que especifica quais funções e eventos devem ser implementados num smart contract para serem compatíveis com o ERC20. Atualmente, são os seguintes:
interface ERC20Interface {
function totalSupply() external constant returns (uint);
function balanceOf(address tokenOwner) external constant returns (uint
balance);
function allowance(address tokenOwner, address spender) external constant
returns (uint remaining);
function transfer(address to, uint tokens) external returns (bool success);
function approve(address spender, uint tokens) external returns (bool success);
function transferFrom(address from, address to, uint tokens) external returns
(bool success);
event Transfer(address indexed from, address indexed to, uint tokens);
event Approval(address indexed tokenOwner, address indexed spender, uint
tokens);
}
O que a função transfer de um contrato ERC20 deveria retornar?
Durante o desenvolvimento do ERC20, houve longas discussões sobre os valores corretos a serem retornados da função transfer de um contrato ERC20. Havia basicamente duas vertentes nessa discussão. Um lado argumentou que a função transfer deveria retornar falso se ela falhasse em permitir o tratamento do erro num contrato de chamada. O outro lado alegou que um ERC20 deveria reverter a transação no caso de falha, para garantir segurança. Esse assunto nunca foi realmente resolvido e ambos comportamentos atualmente são considerados compatíveis com o ERC20. O comportamento mais comum é uma reversão em caso de falha, especialmente desde que o OpenZeppelin implementou seu token ERC20 por esse meio.
Tudo isso tem pouco a ver com o bug específico que eu quero descrever, mas pode ser útil para fornecer algum contexto.
O Bug
Foi verificado que uma porcentagem significativa do token ERC20 se comporta de outra maneira em relação aos valores de retorno da função transfer.
As funções transfer desses contratos de token (vamos chamá-lo de BadTokens) não retornam nada. A interface compatível com elas, se parece com isso:
interface BadERC20Basic {
function balanceOf(address who) external constant returns (uint);
function transfer(address to, uint value) external;
function allowance(address owner, address spender) external constant returns (uint);
function transferFrom(address from, address to, uint value) external;
function approve(address spender, uint value) external;
event Approval(address indexed owner, address indexed spender, uint value);
event Transfer(address indexed from, address indexed to, uint value);
}
Essa interface não é compatível com o padrão ERC20, o que significa que os contratos que implementam essa interface não são tokens ERC20.
Um ponto importante em ter um padrão de token, é permitir que outros smart contracts interajam com muitos contratos de tokens diferentes por meio de uma interface comum. Existem muitos contratos diferentes que utilizam esse recurso de um token ERC20. As exchanges descentralizadas podem enviar tokens, contratos crowdsale podem aceitar token ERC20 como pagamento, etc. Agora, a questão interessante é: o que acontece se um contrato que espera uma interface ERC20 estiver interagindo com um BadToken que não é compatível com ERC20.
Vamos dar uma olhada nesse cenário:
interface Token {
function transfer() returns (bool);
}
contract GoodToken is Token {
function transfer() returns (bool) { return true; }
}
contract BadToken {
function transfer() {}
}
contract Wallet {
function transfer(address token) {
require(Token(token).transfer());
}
}
Na Solidity, um seletor de função é resultante do seu nome de função e do tipo de parâmetros de input.
selector = bytes4(sha3("transfer()"))
O valor a ser retornado de uma função não parte do seletor de função. Então a função transfer() sem um valor de retorno e a função transfer() returns(bool) possuem o mesmo seletor de função mas, ainda são diferentes. Se você tentasse:
contract BadToken is Token {
function transfer() {}
}
o compilador não aceitaria a função transfer() como uma implementação na interface do Token, por causa do valor ausente a ser retornado. Assim, o GoodToken é uma implementação da interface do Token; o BadToken, não.
O que acontece se nós o chamamos de qualquer maneira?
O contrato de chamada envia uma chamada externa para o BadToken, que processa a chamada, faz a transferência e não retorna um valor de retorno booleano. O contrato de chamada agora procura o valor de retorno na memória, mas como o token não escreveu um valor de retorno na memória, ele vai pegar o que ele encontrar nessa posição na memória, como se fosse o valor de retorno da chamada externa.
Isso é realmente muito ruim: pegar alguns dados que estão em um slot de memória como um valor de retorno não é uma boa ideia.
Por pura coincidência esse problema não veio à tona no passado porque o slot de memória no qual o caller esperava o valor a ser retornado, justapôs ao slot de memória, o seletor de função da chamada no qual estava armazenada. Isso foi interpretado pelo EVM como valor a ser retornado “verdadeiro”. E assim, por pura sorte, o EVM se comportou da maneira com que os programadores pretendiam que ele se comportasse.
Mas esse não é o caso para smart contracts mais novos.
O que mudou?
Desde o hard fork Byzantium do último outubro, o EVM possui um novo opcode (código de operação) chamado RETURNDATASIZE. Esse opcode armazena (como sugere o nome) o tamanho dos dados retornados de uma chamada externa. Esse é um opcode muito útil, já que permite retornar arrays de tamanho dinâmico nas funções de chamada.
Esse opcode foi adotado na atualização do Solidity 0.4.22 (quando definido para o modo pós-Byzantium, que é o padrão). Atualmente, o código verifica o tamanho do valor a ser retornado depois de uma chamada externa e reverte a transação no caso em que os dados de retorno são menores do que o esperado. Essa é uma conduta muito mais segura do que ler dados de algum slot de memória. Mas essa nova conduta é um problema maior para nossos BadTokens.
Quem é afetado.
Para se ter uma ideia de quão abrangente é essa questão, eu peguei a lista de tokens ERC20 enumerados no Etherdelta e consultei seus API verificados no Etherscan. Deste modo, encontrei 130 contratos de token afetados.
Existem alguns grandes nomes na lista:
Tokens que não estão retornando um bool na função transfer.
Apenas os tokens listados na Etherdelta foram verificados.
https://raw.githubusercontent.com/etherdelta/etherdelta.github.io/master/config/main.json
todos esses contratos foram afetados pelo bug de valor de retorno ausente. Informação da Morte sobre esse problema:
https://github.com/ethereum/solidity/issues/4116
{'addr': '0xae616e72d3d89e847f74e8ace41ca68bbf56af79', 'name': 'GOOD', 'decimals': 6}
{'addr': '0x93e682107d1e9defb0b5ee701c71707a4b2e46bc', 'name': 'MCAP', 'decimals': 8}
{'addr': '0xb97048628db6b661d4c2aa833e95dbe1a905b280', 'name': 'PAY', 'decimals': 18}
{'addr': '0x4470bb87d77b963a013db939be332f927f2b992e', 'name': 'ADX', 'decimals': 4}
{'addr': '0xd26114cd6ee289accf82350c8d8487fedb8a0c07', 'name': 'OMG', 'decimals': 18}
{'addr': '0xb8c77482e45f1f44de1745f52c74426c631bdd52', 'name': 'BNB', 'decimals': 18}
{'addr': '0xf433089366899d83a9f26a773d59ec7ecf30355e', 'name': 'MTL', 'decimals': 8}
{'addr': '0xc63e7b1dece63a77ed7e4aeef5efb3b05c81438d', 'name': 'FUCKOLD', 'decimals': 4}
{'addr': '0xab16e0d25c06cb376259cc18c1de4aca57605589', 'name': 'FUCK', 'decimals': 4}
{'addr': '0xe3818504c1b32bf1557b16c238b2e01fd3149c17', 'name': 'PLR', 'decimals': 18}
{'addr': '0xe2e6d4be086c6938b53b22144855eef674281639', 'name': 'LNK', 'decimals': 18}
{'addr': '0x2bdc0d42996017fce214b21607a515da41a9e0c5', 'name': 'SKIN', 'decimals': 6}
{'addr': '0xea1f346faf023f974eb5adaf088bbcdf02d761f4', 'name': 'TIX', 'decimals': 18}
{'addr': '0x177d39ac676ed1c67a2b268ad7f1e58826e5b0af', 'name': 'CDT', 'decimals': 18}
{'addr': '0x56ba2ee7890461f463f7be02aac3099f6d5811a8', 'name': 'CAT', 'decimals': 18}
{'addr': '0x08fd34559f2ed8585d3810b4d96ab8a05c9f97c5', 'name': 'CLRT', 'decimals': 18}
{'addr': '0x2a05d22db079bc40c2f77a1d1ff703a56e631cc1', 'name': 'BAS', 'decimals': 8}
{'addr': '0xdc0c22285b61405aae01cba2530b6dd5cd328da7', 'name': 'KTN', 'decimals': 6}
{'addr': '0x9e77d5a1251b6f7d456722a6eac6d2d5980bd891', 'name': 'BRAT', 'decimals': 8}
{'addr': '0x202e295df742befa5e94e9123149360db9d9f2dc', 'name': 'NIH', 'decimals': 8}
{'addr': '0xfb12e3cca983b9f59d90912fd17f8d745a8b2953', 'name': 'LUCK', 'decimals': 0}
{'addr': '0x94298f1e0ab2dfad6eeffb1426846a3c29d98090', 'name': 'MyB', 'decimals': 8}
{'addr': '0x4355fc160f74328f9b383df2ec589bb3dfd82ba0', 'name': 'OPT', 'decimals': 18}
{'addr': '0x17fd666fa0784885fa1afec8ac624d9b7e72b752', 'name': 'FLIK', 'decimals': 14}
{'addr': '0x422866a8f0b032c5cf1dfbdef31a20f4509562b0', 'name': 'ADST', 'decimals': 0}
{'addr': '0xef68e7c694f40c8202821edf525de3782458639f', 'name': 'LRC', 'decimals': 18}
{'addr': '0xe6923e9b56db1eed1c9f430ea761da7565e260fe', 'name': 'FC', 'decimals': 2}
{'addr': '0xf05a9382a4c3f29e2784502754293d88b835109c', 'name': 'REX', 'decimals': 18}
{'addr': '0x73dd069c299a5d691e9836243bcaec9c8c1d8734', 'name': 'BTE', 'decimals': 8}
{'addr': '0x5cf4e9dfd975c52aa523fb5945a12235624923dc', 'name': 'MPRM', 'decimals': 0}
{'addr': '0x336f646f87d9f6bc6ed42dd46e8b3fd9dbd15c22', 'name': 'CCT', 'decimals': 18}
{'addr': '0x190e569be071f40c704e15825f285481cb74b6cc', 'name': 'FAM', 'decimals': 12}
{'addr': '0x190fb342aa6a15eb82903323ae78066ff8616746', 'name': 'UMC', 'decimals': 6}
{'addr': '0xa6e7172662379f1f4c72108655869abdbb7f7672', 'name': 'JADE', 'decimals': 5}
{'addr': '0xc997d07b0bc607b6d1bcb6fb9d4a5579c466c3e5', 'name': 'FLIP', 'decimals': 0}
{'addr': '0xab6cf87a50f17d7f5e1feaf81b6fe9ffbe8ebf84', 'name': 'MRV', 'decimals': 18}
{'addr': '0xac3da587eac229c9896d919abc235ca4fd7f72c1', 'name': 'TGT', 'decimals': 1}
{'addr': '0x437cf0bf53634e3dfa5e3eaff3104004d50fb532', 'name': 'BTN', 'decimals': 4}
{'addr': '0x8727c112c712c4a03371ac87a74dd6ab104af768', 'name': 'JET', 'decimals': 18}
{'addr': '0x78b7fada55a64dd895d8c8c35779dd8b67fa8a05', 'name': 'ATL', 'decimals': 18}
{'addr': '0x2f4baef93489b09b5e4b923795361a65a26f55e5', 'name': 'XHY', 'decimals': 8}
{'addr': '0xe256bb0b2a3457e54db3a41cf5a8b826aca222a8', 'name': 'ARX', 'decimals': 18}
{'addr': '0xb45a50545beeab73f38f31e5973768c421805e5e', 'name': 'TKR', 'decimals': 18}
{'addr': '0x46eec301d2d00087145d1588282c182bd1890e5c', 'name': 'RSPR', 'decimals': 16}
{'addr': '0x12480e24eb5bec1a9d4369cab6a80cad3c0a377a', 'name': 'SUB', 'decimals': 2}
{'addr': '0xdcb9ff81013c31ff686154b4502ef6bfaa102d2d', 'name': 'GOOC', 'decimals': 8}
{'addr': '0x13ea82d5e1a811f55bda9c86fdd6195a6bd23aed', 'name': 'TFT', 'decimals': 8}
{'addr': '0xeeac3f8da16bb0485a4a11c5128b0518dac81448', 'name': 'TEU', 'decimals': 18}
{'addr': '0x23cb17d7d079518dbff4febb6efcc0de58d8c984', 'name': 'TRV', 'decimals': 16}
{'addr': '0x10b123fddde003243199aad03522065dc05827a0', 'name': 'SYN', 'decimals': 18}
{'addr': '0x0aef06dcccc531e581f0440059e6ffcc206039ee', 'name': 'ITT', 'decimals': 8}
{'addr': '0x0b76544f6c413a555f309bf76260d1e02377c02a', 'name': 'INT', 'decimals': 6}
{'addr': '0x0766e79a6fd74469733e8330b3b461c0320ff059', 'name': 'EXN', 'decimals': 18}
{'addr': '0x3a26746ddb79b1b8e4450e3f4ffe3285a307387e', 'name': 'ETHB', 'decimals': 8}
{'addr': '0xe26517a9967299453d3f1b48aa005e6127e67210', 'name': 'NIMFA', 'decimals': 18}
{'addr': '0x37f014c64d186eaf879c0033846b51924ce42584', 'name': 'MDT', 'decimals': 0}
{'addr': '0xfd784da5c740c617aafb80399fa81b86e1da99a5', 'name': 'ITS', 'decimals': 8}
{'addr': '0xf8fa1a588cd8cd51c3c4d6dc16d2717f6332e821', 'name': 'BXC', 'decimals': 2}
{'addr': '0xdded69d8e28d38d640f6244ab5294f309fd40ce1', 'name': 'LMT', 'decimals': 8}
{'addr': '0xef25e54e1ae9bfd966b9b5cde6880e7a2323a957', 'name': 'SOCIAL', 'decimals': 18}
{'addr': '0x5f6e7fb7fe92ea7822472bb0e8f1be60d6a4ea50', 'name': 'ARTE', 'decimals': 18}
{'addr': '0x8633e144f2d9b9b8bdd12ddb58e4bef1e163a0ce', 'name': 'YEL', 'decimals': 18}
{'addr': '0xe2f45f1660dc99daf3bd06f637ab1e4debc15bde', 'name': 'SGG', 'decimals': 6}
{'addr': '0xba5f11b16b155792cf3b2e6880e8706859a8aeb6', 'name': 'ARN', 'decimals': 8}
{'addr': '0xa6e2f7f33f01fb399e72f3e044196eab7d348012', 'name': 'AMO', 'decimals': 4}
{'addr': '0x22c10728343e9d49ef25080f74a223878a3d4052', 'name': 'DRP2', 'decimals': 8}
{'addr': '0x8e10f6bb9c973d61321c25a2b8d865825f4aa57b', 'name': '0ED', 'decimals': 18}
{'addr': '0xbb1b3e8ddded8165d58b0c192d19cd360682b170', 'name': 'CAS', 'decimals': 2}
{'addr': '0xe58aff48f738b4a719d1790587cdc91a3560d7e1', 'name': 'TMP', 'decimals': 7}
{'addr': '0x87ae38d63a6bbb63e46219f494b549e3be7fc400', 'name': 'LAP', 'decimals': 8}
{'addr': '0x7f2176ceb16dcb648dc924eff617c3dc2befd30d', 'name': 'OHNI', 'decimals': 0}
{'addr': '0x26d5bd2dfeda983ecd6c39899e69dae6431dffbb', 'name': 'ERC20', 'decimals': 18}
{'addr': '0xd9a0658b7cc9ec0c57e8b20c0920d08f17e747be', 'name': 'SAT', 'decimals': 10}
{'addr': '0x999967e2ec8a74b7c8e9db19e039d920b31d39d0', 'name': 'TIE', 'decimals': 18}
{'addr': '0xe3fa177acecfb86721cf6f9f4206bd3bd672d7d5', 'name': 'CTT', 'decimals': 18}
{'addr': '0xc99ddc30bb0cf76b07d90dcb6b267b8352697bef', 'name': 'TDT', 'decimals': 18}
{'addr': '0x01c67791309c71aa4ed373025a0c089696d7c9e4', 'name': 'CCB', 'decimals': 18}
{'addr': '0x12a35383ca24ceb44cdcbbecbeb7baccb5f3754a', 'name': 'CS', 'decimals': 6}
{'addr': '0x6f6deb5db0c4994a8283a01d6cfeeb27fc3bbe9c', 'name': 'SMART', 'decimals': 0}
{'addr': '0xdd007278b667f6bef52fd0a4c23604aa1f96039a', 'name': 'RIPT', 'decimals': 8}
{'addr': '0x93e24ce396a9e7d7de4a5bc616cf5fcab0476626', 'name': 'ZIP', 'decimals': 8}
{'addr': '0x7c53f13699e1f6ef5c699e893a20948bdd2e4de9', 'name': 'DVD', 'decimals': 18}
{'addr': '0x3485b9566097ad656c70d6ebbd1cd044e2e72d05', 'name': 'PNKOLD', 'decimals': 0}
{'addr': '0xea642206310400cda4c1c5b8e7945314aa96b8a7', 'name': 'MINT', 'decimals': 18}
{'addr': '0x0b24fdf35876bbe2a1cc925321b8c301017474d4', 'name': 'JWT', 'decimals': 0}
{'addr': '0x219218f117dc9348b358b8471c55a073e5e0da0b', 'name': 'GRX', 'decimals': 18}
{'addr': '0x70838403ecc194b73e50b70a177b2ef413a2f421', 'name': 'BZX', 'decimals': 18}
{'addr': '0x9e386da8cdfcf8b9e7490e3f2a4589c570cb2b2f', 'name': 'RPIL', 'decimals': 8}
{'addr': '0x82b0e50478eeafde392d45d1259ed1071b6fda81', 'name': 'DNA', 'decimals': 18}
{'addr': '0x1844b21593262668b7248d0f57a220caaba46ab9', 'name': 'PRL', 'decimals': 18}
{'addr': '0x149a23f3d1a1e61e1e3b7eddd27f32e01f9788c7', 'name': 'CARE', 'decimals': 18}
{'addr': '0x90b1b771d0814d607da104b988efa39288219d62', 'name': 'MEDI', 'decimals': 18}
{'addr': '0xfad572db566e5234ac9fc3d570c4edc0050eaa92', 'name': 'BTHE', 'decimals': 18}
{'addr': '0xd317ff47dc7e1423e5e050870a66332833e5fd88', 'name': 'PNY', 'decimals': 0}
{'addr': '0xcc34366e3842ca1bd36c1f324d15257960fcc801', 'name': 'BON', 'decimals': 18}
{'addr': '0x1d9e20e581a5468644fe74ccb6a46278ef377f9e', 'name': 'CDRT', 'decimals': 8}
{'addr': '0x7b69b78cc7fee48202c208609ae6d1f78ce42e13', 'name': 'GOAL', 'decimals': 18}
{'addr': '0xffb99f90bcd96fe743796fd8eefaaa6617753e79', 'name': 'MMC2', 'decimals': 0}
{'addr': '0x5121e348e897daef1eef23959ab290e5557cf274', 'name': 'AI', 'decimals': 18}
{'addr': '0x4632091b0dd0e0902d1fe0534e16eb7b20328d70', 'name': 'ULT', 'decimals': 18}
{'addr': '0x0f9b1d1d39118480cf8b9575419ea4e5189c88dd', 'name': 'WET', 'decimals': 0}
{'addr': '0x30cc0e266cf33b8eac6a99cbd98e39b890cfd69b', 'name': 'CLASSY', 'decimals': 16}
{'addr': '0x9d5b592b687c887a5a34df5f9207adb2c2db3aec', 'name': 'ETBT', 'decimals': 18}
{'addr': '0x0f513ffb4926ff82d7f60a05069047aca295c413', 'name': 'XSC', 'decimals': 18}
{'addr': '0x0bb217e40f8a5cb79adf04e1aab60e5abd0dfc1e', 'name': 'SWFTC', 'decimals': 8}
{'addr': '0x679badc551626e01b23ceecefbc9b877ea18fc46', 'name': 'CCO', 'decimals': 18}
{'addr': '0x8ce9411df545d6b51a9bc52a89e0f6d1b54a06dd', 'name': 'ABS', 'decimals': 0}
{'addr': '0xa0e743c37c470ab381cf0e87b6e8f12ef19586fd', 'name': 'CRYPHER', 'decimals': 18}
{'addr': '0x21692a811335301907ecd6343743791802ba7cfd', 'name': 'ADU', 'decimals': 18}
{'addr': '0xe42ba5558b00d2e6109cc60412d5d4c9473fe998', 'name': 'IMC', 'decimals': 18}
{'addr': '0x2eb86e8fc520e0f6bb5d9af08f924fe70558ab89', 'name': 'LGR', 'decimals': 8}
{'addr': '0xe25bcec5d3801ce3a794079bf94adf1b8ccd802d', 'name': 'MAN', 'decimals': 18}
{'addr': '0x2d0e95bd4795d7ace0da3c0ff7b706a5970eb9d3', 'name': 'SOC', 'decimals': 18}
{'addr': '0xa33e729bf4fdeb868b534e1f20523463d9c46bee', 'name': '¢', 'decimals': 10}
{'addr': '0x38c6a68304cdefb9bec48bbfaaba5c5b47818bb2', 'name': 'HPB', 'decimals': 18}
{'addr': '0x81c9151de0c8bafcd325a57e3db5a5df1cebf79c', 'name': 'DAT', 'decimals': 18}
{'addr': '0x8542325b72c6d9fc0ad2ca965a78435413a915a0', 'name': 'SHL', 'decimals': 18}
{'addr': '0x2f85e502a988af76f7ee6d83b7db8d6c0a823bf9', 'name': 'LATX', 'decimals': 8}
{'addr': '0x6ba460ab75cd2c56343b3517ffeba60748654d26', 'name': 'UP', 'decimals': 8}
{'addr': '0xfae4ee59cdd86e3be9e8b90b53aa866327d7c090', 'name': 'CPC', 'decimals': 18}
{'addr': '0x7703c35cffdc5cda8d27aa3df2f9ba6964544b6e', 'name': 'PYLNT', 'decimals': 18}
{'addr': '0xa44e5137293e855b1b7bc7e2c6f8cd796ffcb037', 'name': 'SENT', 'decimals': 8}
{'addr': '0x5e6b6d9abad9093fdc861ea1600eba1b355cd940', 'name': 'ITC', 'decimals': 18}
{'addr': '0x48df4e0296f908ceab0428a5182d19b31fc037d6', 'name': 'FRV', 'decimals': 8}
{'addr': '0x8a99ed8a1b204903ee46e733f2c1286f6d20b177', 'name': 'NTO', 'decimals': 18}
{'addr': '0x3543638ed4a9006e4840b105944271bcea15605d', 'name': 'UUU', 'decimals': 18}
{'addr': '0x622dffcc4e83c64ba959530a5a5580687a57581b', 'name': 'AUTO', 'decimals': 18}
{'addr': '0xe120c1ecbfdfea7f0a8f0ee30063491e8c26fedf', 'name': 'SUR', 'decimals': 8}
{'addr': '0xbbff862d906e348e9946bfb2132ecb157da3d4b4', 'name': 'SS', 'decimals': 18}
{'addr': '0x1a95b271b0535d15fa49932daba31ba612b52946', 'name': 'MNE', 'decimals': 8}
Os maiores tokens (por Market Cap) na lista são:
Binance Coin $1.587.146.847
OmiseGO $1.127.641.627
Mas, existem muitos pequenos e médios tokens na lista também.
Qual é o risco?
Como descrito acima, o maior risco é que um smart contract que seja compilado com solc ≥ 0.4.22 , que esteja esperando uma interface ERC20, não consiga interagir com nossos BadTokens. Isso pode significar que os tokens que são enviados para tal contrato, ficarão presos lá para sempre, mesmo que o contrato tenha uma função para transferir token ERC20. Existem muitos cenários diferentes onde os contratos, manipulando tokens ERC20, possam se deparar com esse bug. Um exemplo é que você não poderia usar exchanges descentralizadas que compilassem esse contrato com solc ≥ 0.4.22 com seu BadToken.
Como isso pode acontecer?
Essa questão é claramente um bug no contrato de token. A mudança no Solidity só trouxe à luz do dia esse bug. Uma razão pela qual existam tantos BadTokens é que num ponto OpenZeppelin implementou a interface errada em sua estrutura. Entre 17 de março de 2017 e 13 de julho de 2017 a interface estava errada:
Então, a maioria dos BadTokens usou a implementação defeituosa do OpenZeppelin da interface do ERC20.
E agora?
Existem duas maneiras de consertar esse bug e eu penso que elas precisam ser abordadas em paralelo.
De um lado, os times dos contratos de token afetados precisam consertar seus contratos. Isso pode ser conseguido fazendo um redeploy ou atualizando o contrato, se este tiver alguma capacidade de atualizar o contrato programado nele.
Outra possibilidade é fazer um wrap do contrato de token com um contrato que chama a função de transferência ruim e retorna a função de transferência boa. Existem diferentes propostas para esses wrappers. Exemplo:
/*
* AVISO: Proof of concept. Não use na produção. Sem garantia.
*
* Solução alternativa de transferência segura para tokens não totalmente compatíveis com ERC20.
* Bem, ERC20 tem muitas revisões,quem sou eu para dizer que não é compatível :)
*
* Leia o texto a seguir para mais explicações: https://github.com/ethereum/solidity/issues/4116
*
* Em resumo:
* O padrão final de ERC20 (IIRC) requer que a função transfer retorne um valor booleano. Alguns
* tokens não retornam nada e os callers assumem que esta interface pode ter um comportamento inesperado.
*
* A operação em detalhes:
* O caller espera que quem chama retorne um booleano, que significa retornar 32 bytes definidos para 0x00..01 (true) ou
* 0x0-0..00 (false). Com pre-Byzantium VMs isso só foi possível estabelecendo um espaço de memória para ser preenchido.
* Contudo, não havia uma forma de dizer se aquele espaço havia sido tocado.
*
* Nós preenchemos essa memória antecipadamente com um valor conhecido (0x00..00ff) que um booleano não pode ter.
* Se após uma CHAMADA de baixo nível ser bem sucedida, esse valor persistir, nós assumimos que seja um token ERC20
* antigo e consideramos que seja um sucesso. Do contrário, levamos em consideração o valor booleano retornado.
* Licença (principalmente por causa da declaração referente às garantias):
*
* A permissão é garantida gratuitamente, para qualquer pessoa que obtenha uma cópia deste software e arquivos de
* documentação relacionada (o "Software"), para lidar com o Software sem restrições, incluindo direitos ilimitados para
* usar, copiar, modificar, mesclar, publicar, distribuir, sublicense e/ou vender cópias do Software e permitir a pessoas
* a quem o Software é fornecido para tal, sujeitos às seguintes condições:
*
* O aviso de direitos autorais acima e esse aviso de permissão devem ser incluídos em todas as cópias e partes substanciais
* do Software.
*
* O SOFTWARE É FORNECIDO "COMO ESTÁ", SEM GARANTIA DE QUALQUER TIPO, EXPRESSA OU
* IMPLÍCITA, INCLUINDO MAS NÃO LIMITADO ÀS GARANTIAS DE COMERCIALIZAÇÃO, ADEQUAÇÃO A UM
* DETERMINADO FIM E NÃO VIOLAÇÃO. EM NENHUMA CIRCUNSTÂNCIA OS AUTORES OU DETENTORES DE
* DIREITOS AUTORAIS SERÃO RESPONSÁVEIS POR QUALQUER REIVINDICAÇÃO, DANOS OU OUTRA
* RESPONSABILIDADE, SEJA EM UMA AÇÃO DE CONTRATO, ATO ILÍCITO OU DE OUTRA FORMA, DECORRENTE
* DE, EM CONEXÃO OU NÃO COM O SOFTWARE OU O USO OU OUTRAS NEGOCIAÇÕES NO PROGRAMAS.
*/
library ERC20SafeTransfer {
function safeTransfer(address _tokenAddress, address _to, uint256 _value) internal returns (bool success) {
// note: both of these could be replaced with manual mstore's to reduce cost if desired
bytes memory msg = abi.encodeWithSignature("transfer(address,uint256)", _to, _value);
uint msgSize = msg.length;
assembly {
// pre-set scratch space to all bits set
mstore(0x00, 0xff)
// note: this requires tangerine whistle compatible EVM
if iszero(call(gas(), _tokenAddress, 0, add(msg, 0x20), msgSize, 0x00, 0x20)) { revert(0, 0) }
switch mload(0x00)
case 0xff {
// token is not fully ERC20 compatible, didn't return anything, assume it was successful
success := 1
}
case 0x01 {
success := 1
}
case 0x00 {
success := 0
}
default {
// unexpected value, what could this be?
revert(0, 0)
}
}
}
}
interface ERC20 {
function transfer(address _to, uint256 _value) returns (bool success);
}
contract TestERC20SafeTransfer {
using ERC20SafeTransfer for ERC20;
function ping(address _token, address _to, uint _amount) {
require(ERC20(_token).safeTransfer(_to, _amount));
}
}
Por outro lado, desenvolvedores que estão escrevendo contratos que lidam com tokens ERC20 precisam estar cientes desse bug, de forma que possam antecipar um comportamento inesperado dos BadTokens para lidar com eles. Isso pode ser feito esperando a interface BadERC20 e verificando os dados de retorno depois da chamada para determinar se chamamos um GoodToken ou um BadToken.
pragma solidity ^0.4.24;
/*
* AVISO: Proof of concept. Não use na produção. Sem garantia
*/
interface BadERC20 {
function transfer(address to, uint value) external;
}
contract BadERC20Aware {
function safeTransfer(address token, address to , uint value) public returns (bool
result) {
BadERC20(token).transfer(to,value);
assembly {
switch returndatasize()
case 0 { // Este é nosso BadToken
result := not(0) // o resultado é verdadeiro
}
case 32 { // Este é nosso GoodToken
returndatacopy(0, 0, 32)
result := mload(0) // resultado == dados de retorno da chamada
externa
}
default { // Este não é um token ERC20
revert(0, 0)
}
}
require(result); // revert() se o resultado for falso
}
}
Conclusão
Eu penso que não há razão para pânico nesse momento, o ciclo de atualização das exchanges descentralizadas e de outros grandes contratos é muito longo. No entanto, esse bug precisa ser corrigido logo que possível.
Também há um artigo bastante informativo sobre esse assunto em:
Você pode ler essa publicação no github aqui: https://github.com/ethereum/solidity/issues/4116
Agradecimentos a Christian , Gérard e Eva pela ajuda nesse artigo
Sobre lukas-berlin
Eu sou um desenvolvedor em Solidity e auditor com sede em Berlim. Também sou CTO da sicos.io e organizador do meetup de Solidity em Berlim. (www.solidity.berlin)
5 Jun, 2018
Este artigo publicado em Coinmonks foi escrito por Lukas Cremer e traduzido por Fátima Lima. Você pode acessar o original aqui.
Oldest comments (0)